How to Add a Command-Line Interface (CLI) to Your TypeScript Project Using Commander

Person coding on laptop

Command-line interfaces (CLI) are powerful tools that can significantly enhance the usability and automation capabilities of your application. By adding a CLI to your TypeScript project, you can provide users with a simple and efficient way to interact with your application through the terminal. In this article, I will guide you through the process of adding a CLI to an existing TypeScript project using the popular commander library. This article assumes that you have already set up a TypeScript project with testing using Jest.

If you run into EACCES permission errors I recommend reading this article about how to solve it.

Table of Contents

  1. Introduction to Commander
  2. Installing Commander
  3. Creating the CLI Entry Point
  4. Defining Commands and Options
  5. Building and Distributing Your CLI
  6. Conclusion

Introduction to Commander

Commander.js is a lightweight, expressive, and versatile library for building command-line interfaces in Node.js. It provides a comprehensive set of features, including:

  • Command and option parsing
  • Automated help generation
  • Nested subcommands
  • Customizable error handling

By leveraging Commander, you can quickly develop a robust and user-friendly CLI for your TypeScript project.

Installing Commander

To get started, you need to install Commander. Open your terminal and navigate to your project directory, then run the following command:

# Use 'sudo' keyword if necessary
npm install commander

Creating the CLI Entry Point

In your src folder, create a new file called cli.ts. This file will serve as the entry point for your CLI. Here is a basic example:

#!/usr/bin/env node

import { program } from 'commander';

program
  .description('My TypeScript CLI');

program.parse(process.argv);

This script sets up the basic structure of your CLI, including a description. The #!/usr/bin/env node shebang line at the top allows the script to be executed as a standalone executable.

Defining Commands and Options

Next, you will define the commands and options for your CLI. Since this CLI was created for an existing TypeScript project let’s import your functions. In the below example I’m importing addNewItem and throttle. Complete example is shared in my GitHub:

import { program } from 'commander';
import { addNewItem, throttle } from './script';

program
  .command('add <item>')
  .description('Add item')
  .action((item: string) => {
    // Initialize array
    let array: string[] = [];

    //Define throttle function
    const throttledAddNewItem = throttle(() => {addNewItem(array, item); console.log(array);}, 1000);

    // Simulate rapid calls
    throttledAddNewItem();
    throttledAddNewItem();
    throttledAddNewItem();
    
  });
  

program.parse(process.argv);

Building and Distributing Your CLI

To make your CLI executable, you need to compile your TypeScript code to JavaScript. If necessary update your tsconfig.json to include the src directory where your cli.ts lives:

{
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "module": "commonjs",
    "target": "es6",
    "strict": true
  },
  "include": [
    "src/**/*",
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

Then, add a build script to your package.json:

"scripts": {
  "cli": "node dist/cli.js"
}

Compile your code with TypeScript compiler:

tsc src/cli.ts --outDir dist

Then, link your package globally:

npm link

You can now run your CLI commands from anywhere using the npm run cli command.

Conclusion

Adding a CLI to your existing TypeScript project using Commander is a straightforward process that can significantly enhance the usability of your application. By following the steps outlined in this article, you can create a robust and user-friendly CLI that allows users to interact with your application through the terminal. Remember to write tests to ensure the reliability of your CLI commands and consider distributing your CLI as a global npm package for broader accessibility. Happy coding!