Building CLI tool development patterns with TypeScript an...
This guide provides a structured approach to building a CLI tool with TypeScript, focusing on cross-platform compatibility, error handling, and integration patterns. Follow these steps to implement a production-ready developer tool.
Initialize project structure
Create a new project directory and configure TypeScript with a tsconfig.json file. Install required dependencies including commander and cross-env for platform-specific commands.
mkdir devtool-cli && cd $_
npm init -y
tsconfig.json
npm install --save commander cross-env⚠ Common Pitfalls
- •Forgetting to add 'type': 'module' in package.json for ES modules
- •Incorrect TypeScript target configuration causing runtime errors
Define CLI command structure
Create a main.ts file that initializes the CLI with commander. Define base commands and subcommands with appropriate options and descriptions.
import { Command } from 'commander';
const program = new Command();
program.command('init <project-name>').description('Initialize new project').action((name) => {
console.log(`Initializing project: ${name}`);
});
program.parse(process.argv);⚠ Common Pitfalls
- •Not using .parse() to trigger command execution
- •Missing required options in subcommands
Implement platform-specific logic
Use the os and path modules to handle platform-specific file paths and command execution. Add environment checks for Windows/MacOS/Linux.
import * as os from 'os';
import * as path from 'path';
const configPath = os.platform() === 'win32'
? path.join(process.env.APPDATA || '', 'devtool')
: path.join(os.homedir(), '.devtool');⚠ Common Pitfalls
- •Hardcoding file paths instead of using platform-aware modules
- •Ignoring environment variable differences between OSes
Add error handling patterns
Implement centralized error handling with try/catch blocks. Create custom error classes for different failure scenarios.
class CLIError extends Error {
constructor(public code: number, message: string) {
super(message);
}
}
try {
// command logic
} catch (err) {
console.error(new CLIError(1, err.message));
process.exit(1);
}⚠ Common Pitfalls
- •Not differentiating between user errors and system errors
- •Ignoring unhandled promise rejections
Create test suite
Write unit tests for core commands using Jest. Test command parsing, error conditions, and platform-specific behavior.
import { execSync } from 'child_process';
test('version command', () => {
const output = execSync('devtool --version').toString();
expect(output).toMatch(/v\d+\.\d+\.\d+/);
});⚠ Common Pitfalls
- •Not mocking process.exit() in tests
- •Missing edge case testing for invalid inputs
Package for distribution
Configure npm scripts for building and publishing. Create a .npmignore file to exclude development files.
{
"scripts": {
"build": "tsc",
"publish": "npm publish --access public"
},
"files": ["dist"]
}⚠ Common Pitfalls
- •Including source files in the published package
- •Forgetting to update the version field before publishing
What you built
By following these steps, you've created a CLI tool with robust error handling, cross-platform support, and test coverage. Focus on maintaining clear command hierarchies and consistent error messaging to improve developer experience.