{"id":13902840,"url":"https://github.com/jmcdo29/nest-commander","last_synced_at":"2025-05-14T05:11:39.939Z","repository":{"id":37021552,"uuid":"328917508","full_name":"jmcdo29/nest-commander","owner":"jmcdo29","description":"A module for using NestJS to build up CLI applications","archived":false,"fork":false,"pushed_at":"2025-05-11T01:26:31.000Z","size":8371,"stargazers_count":457,"open_issues_count":28,"forks_count":60,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-05-11T12:41:26.875Z","etag":null,"topics":["cli","cli-applications","cli-command","commander","hacktoberfest","nestjs","nestjs-commander"],"latest_commit_sha":null,"homepage":"https://nest-commander.jaymcdoniel.dev/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jmcdo29.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["jmcdo29"]}},"created_at":"2021-01-12T08:18:02.000Z","updated_at":"2025-05-04T07:57:23.000Z","dependencies_parsed_at":"2023-10-11T01:15:58.764Z","dependency_job_id":"c44f3fd4-9921-4b45-97b3-5d5251599179","html_url":"https://github.com/jmcdo29/nest-commander","commit_stats":{"total_commits":1218,"total_committers":38,"mean_commits":32.05263157894737,"dds":0.6124794745484401,"last_synced_commit":"d01e8778cdc985d00c1b626636330a07bc778f3f"},"previous_names":[],"tags_count":1531,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmcdo29%2Fnest-commander","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmcdo29%2Fnest-commander/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmcdo29%2Fnest-commander/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmcdo29%2Fnest-commander/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jmcdo29","download_url":"https://codeload.github.com/jmcdo29/nest-commander/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253678779,"owners_count":21946314,"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":["cli","cli-applications","cli-command","commander","hacktoberfest","nestjs","nestjs-commander"],"created_at":"2024-08-06T22:01:27.363Z","updated_at":"2025-05-14T05:11:34.908Z","avatar_url":"https://github.com/jmcdo29.png","language":"TypeScript","funding_links":["https://github.com/sponsors/jmcdo29","https://www.buymeacoffee.com/jmcdo29"],"categories":["TypeScript","cli"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \n![CI](https://github.com/jmcdo29/nest-commander/workflows/CI/badge.svg) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) [![Coffee](https://badgen.net/badge/Buy%20Me/A%20Coffee/purple?icon=kofi)](https://www.buymeacoffee.com/jmcdo29) [![codebeat badge](https://codebeat.co/badges/886435cf-0ace-403b-8f9c-3e4eb99fbd5d)](https://codebeat.co/projects/github-com-jmcdo29-nest-commander-main)\n  \n  \u003cp align=\"center\"\u003e\n    \u003ca href=\"https://jmcdo29.github.io/nest-commander\" target=\"blank\"\u003e\u003cimg src=\"https://nest-commander.jaymcdoniel.dev/img/nest-commander-final.svg\" width=\"220\" alt=\"Nest Commander Logo\" /\u003e\u003c/a\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n\n# NestJS Commander\n\nHave you been building amazing applications with [NestJS](https://docs.nestjs.com/)? Do you want\nthat same structure for absolutely everything you're working with? Have you always wanted to build\nup some sweet CLI application but don't really know where to start? This is the solution. A package\nto bring building CLI applications to the Nest world with the same structure that you already know\nand love :heart: Built on top of the popular [Commander](https://github.com/tj/commander.js)\npackage.\n\n## Installation\n\nBefore you get started, you'll need to install a few packages. First and foremost, this one:\n`nest-commander` (name pending). You'll also need to install `@nestjs/common` and `@nestjs/core` as\nthis package makes use of them under the hood, but doesn't want to tie you down to a specific\nversion, yay peerDependencies!\n\n```sh\nnpm i nest-commander @nestjs/common @nestjs/core\n# OR\nyarn add nest-commander @nestjs/common @nestjs/core\n# OR\npnpm i nest-commander @nestjs/common @nestjs/core\n```\n\n## A Command File\n\n`nest-commander` makes it easy to write new command line applications with\n[decorators](https://www.typescriptlang.org/docs/handbook/decorators.html) via the `@Command()`\ndecorator for classes and the `@Option()` decorator for methods of that class. Every command file\n_should_ implement the `CommandRunner` abstract class and _should_ be decorated with a `@Command()`\ndecorator.\n\n### CommandRunner\n\nEvery command is seen as an `@Injectable()` by Nest, so your normal Dependency Injection still works\nas you would expect it to (woohoo!). The only thing to take note of is the abstract class\n`CommandRunner`, which should be implemented by each command. The `CommandRunner` abstract class\nensures that all commands have a `run` method that return a `Promise\u003cvoid\u003e` and takes in the\nparameters `string[], Record\u003cstring, any\u003e`. The `run` command is where you can kick all of your\nlogic off from, it will take in whatever parameters did not match option flags and pass them in as\nan array, just in case you are really meaning to work with multiple parameters. As for the options,\nthe `Record\u003cstring, any\u003e`, the names of these properties match the `name` property given to the\n`@Option()` decorators, while their value matches the return of the option handler. If you'd like\nbetter type safety, you are welcome to create an interface for your options as well. You can view\nhow the [Basic Command test](./integration/basic/src/basic.command.ts) manages that if interested.\n\n### @Command()\n\nThe `@Command()` decorator is to define what CLI command the class is going to manage and take care\nof. The decorator takes in an object to define properties of the command. The options passed here\nwould be the same as the options passed to a new `command` for Commander\n\n| property        | type                   | required | description                                                                                                                             |\n| --------------- | ---------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |\n| name            | string                 | true     | the name of the command                                                                                                                 |\n| arguments       | string                 | false    | Named arguments for the command to work with. These can be required `\u003c\u003e` or optional `[]`, but do not map to an option like a flag does |\n| description     | string                 | false    | the description of the command. This will be used by the `--help` or `-h` flags to have a formalized way of what to print out           |\n| argsDescription | Record\u003cstring, string\u003e | false    | An object containing the description of each argument. This will be used by `-h` or `--help`                                            |\n| Options         | CommandOptions         | false    | Extra options to pass on down to commander                                                                                              |\n\nFor mor information on the `@Command()` and `@Option()` parameters, check out the\n[Commander docs](https://github.com/tj/commander.js).\n\n### @Option()\n\nOften times you're not just running a single command with a single input, but rather you're running\na command with multiple options and flags. Think of something like `git commit`: you can pass a\n`--amend` flag, a `-m` flag, or even `-a`, all of these change how the command runs. These flags are\nable to be set for each command using the `@Option()` decorator on a method for how that flag should\nbe parsed. Do note that every command sent in via the command line is a raw string, so if you need\nto transform that string to a number or a boolean, or any other type, this handler is where it can\nbe done. See the [putting it all together](#putting-it-all-together) for an example. The `@Option()`\ndecorator, like the `@Command()` one, takes in an object of options defined in the table below\n\n| property     | type              | required | description                                                                                                         |\n| ------------ | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------- |\n| flags        | string            | true     | a string that represents the option's incoming flag and if the option is required (using \u003c\u003e) or optional (using []) |\n| description  | string            | false    | the description of the option, used if adding a `--help` flag                                                       |\n| defaultValue | string or boolean | false    | the default value for the flag                                                                                      |\n\nUnder the hood, the method that the`@Option()` is decorating is the custom parser passed to\ncommander for how the value should be parsed. This means if you want to parse a boolean value, the\nbest way to do so would be to use `JSON.parse(val)` as `Boolean('false')` actually returns `true`\ninstead of the expected `false`.\n\n## Running the Command\n\nSimilar to how in a NestJS application we can use the `NestFactory` to create a server for us, and\nrun it using `listen`, the `nest-commander` package exposes a simple to use API to run your server.\nImport the `CommandFactory` and use the `static` method `run` and pass in the root module of your\napplication. This would probably look like below\n\n```ts\nimport { CommandFactory } from 'nest-commander';\nimport { AppModule } from './app.module';\n\nasync function bootstrap() {\n  await CommandFactory.run(AppModule);\n}\n\nbootstrap();\n```\n\nBy default, Nest's logger is disabled when using the `CommandFactory`. It's possible to provide it\nthough, as the second argument to the `run` function. You can either provide a custom NestJS logger,\nor an array of log levels you want to keep - it might be useful to at least provide `['error']`\nhere, if you only want to print out Nest's error logs.\n\n```ts\nimport { CommandFactory } from 'nest-commander';\nimport { AppModule } from './app.module';\nimport { LogService } './log.service';\n\nasync function bootstrap() {\n  await CommandFactory.run(AppModule, new LogService());\n\n  // or, if you only want to print Nest's warnings and errors\n  await CommandFactory.run(AppModule, ['warn', 'error']);\n}\n\nbootstrap();\n```\n\nAnd that's it. Under the hood, `CommandFactory` will worry about calling `NestFactory` for you and\ncalling `app.close()` when necessary, so you shouldn't need to worry about memory leaks there. If\nyou need to add in some error handling, there's always `try/catch` wrapping the `run` command, or\nyou can chain on some `.catch()` method to the `bootstrap()` call.\n\n## Testing\n\nSo what's the use of writing a super awesome command line script if you can't test it super easily,\nright? Fortunately, `nest-commander` has some utilities you can make use of that fits in perfectly\nwith the NestJS ecosystem, it'll feel right at home to any Nestlings out there. Instead of using the\n`CommandFactory` for building the command in test mode, you can use `CommandTestFactory` and pass in\nyour metadata, very similarly to how `Test.createTestingModule` from `@nestjs/testing` works. In\nfact, it uses this package under the hood. You're also still able to chain on the `overrideProvider`\nmethods before calling `compile()` so you can swap out DI pieces right in the test.\n[A nice example of this can be seen in the basic.command.factory.spec.ts file](./integration/basic/test/basic.command.factory.spec.ts).\n\n## Putting it All Together\n\nThe following class would equate to having a CLI command that can take in the subcommand `basic` or\nbe called directly, with `-n`, `-s`, and `-b` (along with their long flags) all being supported and\nwith custom parsers for each option. The `--help` flag is also supported, as is customary with\ncommander.\n\n```ts\nimport { Command, CommandRunner, Option } from 'nest-commander';\nimport { LogService } from './log.service';\n\ninterface BasicCommandOptions {\n  string?: string;\n  boolean?: boolean;\n  number?: number;\n}\n\n@Command({ name: 'basic', description: 'A parameter parse' })\nexport class BasicCommand extends CommandRunner {\n  constructor(private readonly logService: LogService) {\n    super();\n  }\n\n  async run(passedParam: string[], options?: BasicCommandOptions): Promise\u003cvoid\u003e {\n    if (options?.boolean !== undefined \u0026\u0026 options?.boolean !== null) {\n      this.runWithBoolean(passedParam, options.boolean);\n    } else if (options?.number) {\n      this.runWithNumber(passedParam, options.number);\n    } else if (options?.string) {\n      this.runWithString(passedParam, options.string);\n    } else {\n      this.runWithNone(passedParam);\n    }\n  }\n\n  @Option({\n    flags: '-n, --number [number]',\n    description: 'A basic number parser'\n  })\n  parseNumber(val: string): number {\n    return Number(val);\n  }\n\n  @Option({\n    flags: '-s, --string [string]',\n    description: 'A string return'\n  })\n  parseString(val: string): string {\n    return val;\n  }\n\n  @Option({\n    flags: '-b, --boolean [boolean]',\n    description: 'A boolean parser'\n  })\n  parseBoolean(val: string): boolean {\n    return JSON.parse(val);\n  }\n\n  runWithString(param: string[], option: string): void {\n    this.logService.log({ param, string: option });\n  }\n\n  runWithNumber(param: string[], option: number): void {\n    this.logService.log({ param, number: option });\n  }\n\n  runWithBoolean(param: string[], option: boolean): void {\n    this.logService.log({ param, boolean: option });\n  }\n\n  runWithNone(param: string[]): void {\n    this.logService.log({ param });\n  }\n}\n```\n\nMake sure the command class is added to a module\n\n```ts\n@Module({\n  providers: [LogService, BasicCommand]\n})\nexport class AppModule {}\n```\n\nAnd now to be able to run the CLI in your main.ts you can do the following\n\n```ts\nasync function bootstrap() {\n  await CommandFactory.run(AppModule);\n}\n\nbootstrap();\n```\n\nAnd just like that, you've got a command line application.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmcdo29%2Fnest-commander","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjmcdo29%2Fnest-commander","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmcdo29%2Fnest-commander/lists"}