← Back to TypeScript Course | Chapter 28: Real World Projects | Lesson 4 of 14

CLI Tool with TypeScript

TypeScript can be used to build command-line tools running on Node.js. Typed argument parsing and service functions make CLI code easier to maintain.
Syntax
typescript
program.option("-p, --port <number>", "description", Number);   // commander
const options = program.opts<{ port: number }>();

Core Concept

A typed CLI tool benefits from TypeScript by making its argument parsing and command structure type-safe, so a typo in an option name or a wrong flag type is caught before the tool ships, not when a user hits it.

Example: Core Concept

typescript
interface Options {
  port: number;
  verbose: boolean;
}
function parseArgs(args: string[]): Options {
  return { port: 3000, verbose: args.includes("--verbose") };
}
console.log(parseArgs(["--verbose"]));

Basic Setup

A basic setup uses a library like commander or yargs (both ship TypeScript types) to define commands and options, compiling the .ts source to a runnable Node script via tsc.

Example: Basic Setup

typescript
// npm install commander
// import { program } from "commander";
console.log("commander/yargs ship TypeScript types for CLI parsing");

Typed Example

A typed example: program.option('-p, --port <number>', 'port number', Number) combined with yargs's inferred Arguments type means argv.port is known to be a number everywhere it's used, not a raw string that needs manual parsing.

Example: Typed Example

typescript
// program.option('-p, --port <number>', 'port number', Number);
interface Argv {
  port: number;
}
const argv: Argv = { port: 8080 };
console.log(argv.port); // known to be a number, not a raw string

Project Usage

In a real project, a CLI's typed command handlers stay maintainable as more commands and flags get added over time, since the compiler catches a handler that forgot to account for a newly-added required option.

Example: Project Usage

typescript
interface Command {
  name: string;
  run: (args: string[]) => void;
}
const commands: Command[] = [
  { name: "build", run: () => console.log("Building...") },
];
commands[0].run([]);

Best Practices

Add a bin field to package.json pointing at your compiled entry file (with a #!/usr/bin/env node shebang) so the tool can be installed globally and run as a real command once built.

Example: Best Practices

typescript
// package.json: { "bin": { "mytool": "./dist/cli.js" } }
// dist/cli.js starts with: #!/usr/bin/env node
console.log("bin field + shebang lets the tool run as a global command");
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Reading process.argv values and treating them as numbers, when they are always strings and need Number(...).
  2. Forgetting the #!/usr/bin/env node line and the bin field in package.json, so the command is not runnable.
  3. Running the .ts file directly instead of the compiled .js output.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.