A professional and feature-rich template for building command-line interface (CLI) applications using TypeScript, Commander.js, and modern development tools.
- π― TypeScript - Full type safety and modern JavaScript features
- π¨ Commander.js - Powerful CLI framework with sub-commands support
- π Inquirer.js - Beautiful interactive prompts
- π Chalk - Colorful terminal output
- π§ Biome - Fast linter and formatter (Prettier + ESLint alternative)
- π¦ pnpm - Fast, disk space efficient package manager
- π Structured Logging - Built-in logger with multiple log levels and file output
- βοΈ Configuration Management - Built-in config system with CRUD operations and validation
- β Zod Validation - Runtime schema validation for configurations
- β±οΈ Timeout Utilities - Async helpers with timeout, retry, and delay functions
- π§ͺ Vitest - Fast unit testing with coverage support
- π‘οΈ Graceful Shutdown - Proper signal handling for clean exits
- π Hot Reload - Development mode with watch support
- Node.js >= 18
- pnpm >= 8 (or use
npm install -g pnpm)
-
Clone or use this template:
git clone https://github.com/usarral/typescript-cli-template.git my-cli cd my-cli -
Install dependencies:
pnpm install
-
Customize your CLI:
- Update
package.jsonwith your CLI name and details - Modify
src/index.tsto change the CLI name - Extend
src/utils/configManager.tswith your custom config properties - Add your commands in
src/commands/
- Update
-
Run in development:
pnpm dev
-
Build for production:
pnpm build pnpm start
# Create a new configuration
demo-cli config create
# List all configurations
demo-cli config list
# Set active configuration
demo-cli config use <name>
# Show current configuration
demo-cli config current
# Delete a configuration
demo-cli config delete [name]# Enable verbose/debug logging
demo-cli --verbose [command]
# Disable colored output
demo-cli --no-color [command]
# Show version
demo-cli --version
# Show help
demo-cli --helptypescript-cli-template/
βββ src/
β βββ index.ts # CLI entry point
β βββ commands/ # Command implementations
β β βββ config/ # Config management commands
β β βββ create.ts # Create new config
β β βββ list.ts # List configs
β β βββ use.ts # Set active config
β β βββ current.ts # Show current config
β β βββ delete.ts # Delete config
β β βββ index.ts # Setup config commands
β βββ utils/ # Utility modules
β βββ Logger.ts # Logger interface
β βββ ChalkLogger.ts # Chalk-based logger implementation
β βββ config.ts # App configuration
β βββ configManager.ts # Configuration file manager
β βββ version.ts # Version helper
βββ dist/ # Compiled JavaScript (generated)
βββ package.json
βββ tsconfig.json
βββ biome.json
βββ README.md
# Development mode with hot reload
pnpm dev:watch
# Run without building
pnpm dev
# Build TypeScript to JavaScript
pnpm build
# Run built version
pnpm start
# Lint code
pnpm lint
# Lint and fix issues
pnpm lint:fix
# Format code
pnpm format
# Run tests
pnpm test
# Run tests with UI
pnpm test:ui
# Run tests with coverage
pnpm test:coverage
# Type check without emitting
pnpm type-check
# Watch mode for TypeScript compilation
pnpm watch
# Clean build directory
pnpm clean- Create a new file in
src/commands/:
import { Command } from "commander";
import { Logger } from "../utils/Logger";
export function myCommand(program: Command): void {
program
.command("mycommand")
.description("My awesome command")
.action(() => {
Logger.info("Executing my command!");
// Your logic here
});
}- Register it in
src/index.ts:
import { myCommand } from "./commands/myCommand";
// ...
myCommand(program);To add custom properties to your configuration:
- Extend the
AppConfiginterface insrc/utils/configManager.ts:
export interface AppConfig {
name: string;
description?: string;
// Add your custom properties:
apiUrl?: string;
apiKey?: string;
timeout?: number;
}- Update the create command in
src/commands/config/create.ts:
// Add prompts for your fields
{
type: "input",
name: "apiUrl",
message: "API URL:",
validate: (input) => input.trim() ? true : "API URL is required",
},- Update the list command in
src/commands/config/list.tsto display your fields.
The built-in logger supports multiple log levels, customization, and file output. The logger uses console.log/info/warn/error internally with colors via Chalk:
import { Logger, LogLevel } from "./utils/Logger";
// Set log level
Logger.setLogLevel(LogLevel.DEBUG);
// Configure logger options (including file output)
Logger.setOptions({
showTimestamp: false, // Disable timestamps
colorize: false, // Disable colors
logFile: "/path/to/app.log", // Enable file logging
});
// Use different log levels
Logger.debug("Debug information");
Logger.info("General information");
Logger.warn("Warning message");
Logger.error("Error occurred");
Logger.fatal("Critical error");
// Log objects (auto-formatted as JSON)
Logger.info("User data:", { id: 1, name: "John" });Always use Logger for all output in your commands:
import { Logger } from "./utils/Logger";
export async function myCommand(): Promise<void> {
Logger.info("Starting operation...");
try {
// Your logic
Logger.info("Operation completed successfully");
} catch (error) {
Logger.error("Operation failed");
Logger.debug("Error details:", error);
}
}The template includes powerful utilities for async operations:
import { withTimeout, retry, delay } from "./utils/timeout";
// Add timeout to any promise
const result = await withTimeout(
fetch('https://api.example.com'),
5000, // 5 second timeout
new Error('API request timed out')
);
// Retry failed operations with exponential backoff
const data = await retry(
() => fetchData(),
{
maxAttempts: 3,
baseDelay: 1000,
maxDelay: 10000,
onRetry: (attempt, error) => {
console.log(`Retry attempt ${attempt}:`, error);
}
}
);
// Simple delay
await delay(1000); // Wait 1 secondConfigurations are validated using Zod schemas:
import { z } from "zod";
import { AppConfigSchema } from "./utils/configManager";
// Extend the base schema for your custom config
const MyConfigSchema = AppConfigSchema.extend({
apiUrl: z.string().url(),
apiKey: z.string().min(1),
timeout: z.number().positive().optional(),
});
// Type-safe config
type MyConfig = z.infer<typeof MyConfigSchema>;- Update package.json:
{
"name": "your-cli-name",
"bin": {
"your-cli": "./dist/index.js"
}
}- Add shebang to
src/index.ts:
#!/usr/bin/env node- Build and test locally:
pnpm build
npm link
your-cli --help- Publish to npm:
npm publish- Target: ES2021
- Module: ES2022 with bundler resolution
- Strict mode enabled
- Source maps and declarations included
- Linting and formatting combined
- Git integration enabled
- Auto-organize imports
- Tab indentation, double quotes
Contributions are welcome! Feel free to:
- Report bugs
- Suggest new features
- Submit pull requests
MIT Β© usarral
This template uses the following excellent projects:
- Commander.js - CLI framework
- Inquirer.js - Interactive prompts
- Chalk - Terminal styling
- Biome - Linter and formatter
- TypeScript - Type safety
Happy coding! π