Skip to content

Gnob/typescript-cli-template

Repository files navigation

TypeScript CLI Template

A production-ready TypeScript monorepo template for building type-safe CLI applications with extensibility for web applications.

✨ Features

  • 🎯 Type-Safe Everything: Strict TypeScript + Zod runtime validation + Optique discriminated unions
  • 📦 Monorepo Architecture: pnpm workspace for code sharing between CLI and future web apps
  • 🔧 Modern Tooling: tsup for building, Biome for quality, Jest for testing
  • ⚡ Type-Safe CLI: Optique with automatic type inference and shell completion
  • 🐛 Flexible Debugging: debug module with namespace-based filtering
  • ⚙️ Multi-Source Config: Defaults → Config file → Environment → CLI args
  • ✨ Excellent DX: Hot reload, auto-completion, helpful error messages
  • 🏗️ Production Ready: Quality gates, strict linting, comprehensive validation

📚 Tech Stack

Purpose Library Version
CLI Parser Optique 0.6.0
Debugging debug 4.4.0
Configuration Conf 15.0.2
Schema Validation Zod 4.1.11
Env Validation envalid 8.1.0
Lint/Format Biome 2.2.5
Build tsup 8.5.0
Test Jest 29.7.0
TypeScript TypeScript 5.9.3
Package Manager pnpm 9.15.4+

📁 Project Structure

typescript-cli-template/
├── apps/
│   ├── cli/                    # CLI application
│   │   ├── src/
│   │   │   ├── cli.ts          # Main entry point
│   │   │   └── commands/       # Command implementations
│   │   │       ├── config.ts   # Config management
│   │   │       └── process.ts  # File processing
│   │   └── package.json
│   └── web/                    # (Future) Web application
├── packages/
│   ├── core/                   # Platform-agnostic business logic
│   │   ├── src/models/         # Zod schemas & types
│   │   └── src/services/       # Business services
│   ├── config/                 # Configuration management
│   │   ├── src/env.ts          # Environment validation (envalid)
│   │   ├── src/schemas.ts      # Config schemas (Zod)
│   │   └── src/store.ts        # Config storage (Conf)
│   └── shared/                 # Shared utilities
│       ├── src/exceptions.ts   # Custom exceptions
│       ├── src/types/          # Common types
│       └── src/utils/          # Utilities (string, array, object)
├── scripts/
│   └── check.sh                # Quality checks runner
├── CLAUDE.md                   # Development guidelines
├── LIBRARIES.md                # Core libraries guide
└── package.json                # Root workspace config

🚀 Quick Start

⚠️ Before You Start: Customize the Template

This template uses myapp as a placeholder. You must rename it to your project name before starting development.

See CUSTOMIZATION.md for detailed instructions.

Quick customization:

# Find all files that need customization
grep -r "TEMPLATE_CUSTOMIZE" . --exclude-dir=node_modules --exclude-dir=dist

# Replace myapp with your project name
# 1. Package names: @myapp/* → @yourproject/*
# 2. Program name: myapp → yourproject
# 3. Env var prefix: MYAPP_ → YOURPROJECT_
# 4. Logger categories: ['myapp', ...] → ['yourproject', ...]

Prerequisites

Installation

# Clone the repository
git clone <your-repo-url>
cd typescript-cli-template

# Install dependencies
pnpm install

# Build all packages
pnpm build

Install CLI Globally (Recommended)

Install the CLI globally to use myapp command from anywhere:

# Install globally (builds and links CLI)
pnpm install:global

# Now use from anywhere
myapp --version
myapp config path
myapp process input.txt -o output.txt

# Uninstall when done
pnpm uninstall:global

Run the CLI

# Development mode (with hot reload)
pnpm --filter @myapp/cli dev config path

# Production mode (if installed globally)
myapp config path

# Production mode (run built version directly)
node apps/cli/dist/cli.js config path

💻 Development

Commands

# Run CLI with hot reload
pnpm --filter @myapp/cli dev [command] [args...]

# Install/update global CLI
pnpm install:global

# Uninstall global CLI
pnpm uninstall:global

# Type check all packages
pnpm typecheck

# Format code
pnpm format

# Lint code
pnpm lint

# Run all quality checks (typecheck + format + lint + build)
./scripts/check.sh

# Build all packages
pnpm build

# Build specific package
pnpm --filter @myapp/core build

# Clean build outputs
pnpm clean

Development Workflow

  1. Make changes to code
  2. Run pnpm typecheck to check types
  3. Run ./scripts/check.sh to verify quality
  4. Auto-fix issues: pnpm biome check --write .

📖 Usage Examples

The examples below assume you've installed the CLI globally with pnpm install:global. For development mode, prefix commands with pnpm --filter @myapp/cli dev.

Configuration Management

# View config file path
myapp config path
# Output: /Users/user/Library/Preferences/myapp/config.json

# Set a configuration value
myapp config set apiKey "sk-1234567890"
# Output: ✓ Configuration saved

# Get a configuration value
myapp config get apiKey
# Output: sk-1234567890

# List all configuration
myapp config list
# Output: { "apiKey": "sk-1234567890" }

# Remove a configuration value
myapp config unset apiKey
# Output: ✓ Configuration value removed

File Processing

# Process a file
myapp process input.txt --output result.txt
# Output:
# ✓ Processing completed
# Processed: 100
# Skipped:   5
# Errors:    0
# Duration:  102.17ms
# Output: result.txt

# Force overwrite existing files
myapp process file.txt -o output.txt --force

# Short flags
myapp process file.txt -o output.txt -f

Debugging with DEBUG Environment Variable

Use the DEBUG environment variable to enable debug output:

# Enable all debug output
DEBUG=myapp:* myapp process file.txt -o output.txt
# Output:
# ✓ Processing completed
# ...
# 2025-10-17 myapp:core:processor Starting processing {...}
# 2025-10-17 myapp:core:processor Processing with options {...}
# 2025-10-17 myapp:core:processor Processing completed {...}

# Enable specific namespace only
DEBUG=myapp:cli:* myapp config set key value
DEBUG=myapp:core:* myapp process file.txt -o output.txt

# Enable multiple namespaces
DEBUG=myapp:cli:config,myapp:core:processor myapp process file.txt -o output.txt

Help and Version

# Show version
myapp --version
# Output: 0.1.0

# Show help
myapp --help

# Show help for specific command
myapp config --help

# Shell completion (bash/zsh/fish)
myapp completion bash

⚙️ Configuration

Configuration Precedence

Configuration values are loaded from multiple sources in this order (later sources override earlier):

  1. Schema Defaults (defined in Zod schemas)
  2. Config File (~/.config/myapp/config.json or MYAPP_CONFIG_DIR)
  3. Environment Variables (prefixed with MYAPP_)
  4. CLI Arguments (command-line flags)

Environment Variables

All environment variables are validated at startup using envalid.

Variable Type Default Description
MYAPP_NO_COLOR boolean false Disable colored output
MYAPP_CONFIG_DIR string OS-specific Custom config directory path
MYAPP_ENDPOINT string https://api.example.com API endpoint URL
MYAPP_API_KEY string - API key for authentication
MYAPP_TIMEOUT number 30000 Request timeout (milliseconds)
MYAPP_TELEMETRY boolean true Enable telemetry collection
DEBUG string - Enable debug output (e.g., myapp:*)

Example: Environment Configuration

# Set environment variables
export MYAPP_ENDPOINT=https://custom.api.com
export MYAPP_API_KEY=your-secret-key
export DEBUG=myapp:*  # Enable debug output

# Run with custom config
myapp process data.txt -o out.txt

📦 Package Overview

@myapp/cli

CLI application built with Optique for type-safe command parsing.

Key Features:

  • Automatic type inference from parsers
  • Built-in shell completion support
  • Discriminated unions for command handling
  • Helpful error messages

@myapp/core

Platform-agnostic business logic that can be shared across CLI and web applications.

import { ProcessorService } from '@myapp/core';

const processor = new ProcessorService();
const result = await processor.process({
  file: 'input.txt',
  output: 'output.txt',
  force: true,
  options: {
    batchSize: 100,
    maxRetries: 3,
  },
});

console.log(`Processed: ${result.processed}`);

@myapp/config

Type-safe configuration management with multi-source loading.

import { loadConfig, store, ENV } from '@myapp/config';

// Load configuration from all sources
const config = loadConfig({ endpoint: 'https://custom.api' });

// Get/set configuration values (supports dot notation)
store.set('log.level', 'debug');
store.set('preferences.theme', 'dark');

const logLevel = store.get<string>('log.level');

// Access environment variables (validated with envalid)
console.log(ENV.MYAPP_NO_COLOR);  // Type-safe!

// Check config file location
console.log(store.path);

@myapp/shared

Common utilities, types, and exceptions used across all packages.

// Exceptions
import { AppError, ValidationError, ConfigError, FileError } from '@myapp/shared';

throw new ValidationError('Invalid input', 'Please check your data format');

// Types
import type { Brand, JsonValue, JsonObject } from '@myapp/shared/types';

type UserId = Brand<string, 'UserId'>;

// Utilities
import {
  deepMerge,
  getByPath,
  setByPath,
  capitalize,
  camelCase,
  chunk,
  unique,
} from '@myapp/shared/utils';

const obj = { user: { name: 'John' } };
const name = getByPath(obj, 'user.name'); // 'John'

const merged = deepMerge({ a: 1 }, { b: 2 }); // { a: 1, b: 2 }

🎯 Development Guidelines

Essential Reading

  • CLAUDE.md - Development principles and coding standards
  • LIBRARIES.md - Core libraries guide and troubleshooting

Core Principles

  1. Strong Typing is Mandatory

    • ❌ Never use any
    • ✅ Zod-first: Define schemas, infer types
    • ✅ Type everything: All params and returns
  2. Code Quality Standards

    • Files: ≤ 300 lines
    • Functions: ≤ 50 lines, ≤ 5 parameters
    • Complexity: ≤ 10 (cyclomatic)
    • Testing: Write tests for all features
  3. Best Practices

    • Fail fast with validation at boundaries
    • Explicit over implicit
    • Immutability by default
    • Use debug for development debugging, console for user output

Example: Zod-First Development

import { z } from 'zod';

// 1. Define schema first
const UserSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().int().positive().optional(),
});

// 2. Infer type from schema
type User = z.infer<typeof UserSchema>;

// 3. Use for validation
function createUser(data: unknown): User {
  return UserSchema.parse(data); // Runtime validation!
}

// 4. Safe parsing
function tryCreateUser(data: unknown): User | null {
  const result = UserSchema.safeParse(data);
  if (!result.success) {
    console.error('Validation failed:', result.error);
    return null;
  }
  return result.data;
}

✅ Quality Checks

Run comprehensive quality checks before committing:

# Run all checks at once
./scripts/check.sh

# Or run individually
pnpm typecheck              # TypeScript strict mode
pnpm biome format .         # Check formatting
pnpm biome lint .           # Run linter
pnpm biome check .          # Format + lint + organize imports
pnpm build                  # Build all packages

Auto-fix Issues

# Auto-format code
pnpm biome format --write .

# Auto-fix linting issues
pnpm biome lint --write .

# Auto-fix everything (format + lint + organize imports)
pnpm biome check --write .

Enforced Quality Rules

  • ✅ TypeScript strict mode enabled
  • ✅ Cyclomatic complexity ≤ 10
  • ✅ No unused imports/variables
  • ✅ No any types
  • ✅ Proper error handling
  • ✅ All schemas validated with Zod

🌐 Extending for Web

This monorepo is designed for easy extension to web applications:

1. Add Web Application

# Create new web app
mkdir -p apps/web
cd apps/web
pnpm init

# Install web framework (e.g., Next.js, Vite + React)
pnpm add next react react-dom

# Add workspace dependencies
pnpm add @myapp/core @myapp/shared

2. Share Business Logic

// apps/web/src/services/processor.ts
import { ProcessorService } from '@myapp/core';
import createDebug from 'debug';

const debug = createDebug('myapp:web:processor');

export async function processInBrowser(data: ProcessInput) {
  debug('Processing in browser', data);

  const processor = new ProcessorService();
  const result = await processor.process(data);

  debug('Processing complete', result);
  return result;
}

📋 Common Tasks

Adding a New Command

  1. Create command file in apps/cli/src/commands/:
import { command, object, constant, argument } from '@optique/core/parser';
import { string } from '@optique/core/valueparser';
import type { InferValue } from '@optique/core/parser';

export const myCommand = command(
  'my-command',
  object({
    command: constant('my-command' as const),
    arg: argument(string({ metavar: 'ARG' })),
  }),
);

export type MyCommand = InferValue<typeof myCommand>;

export function handleMyCommand(cmd: MyCommand): void {
  console.log(`Handling: ${cmd.arg}`);
}
  1. Register in apps/cli/src/cli.ts:
import { myCommand, handleMyCommand } from './commands/my-command.js';

const parser = or(configCommand, processCommand, myCommand);

// In main():
if (result.command === 'my-command') {
  handleMyCommand(result);
}

Adding a New Package

  1. Create package structure:
mkdir -p packages/my-package/src
cd packages/my-package
pnpm init
  1. Configure package.json:
{
  "name": "@myapp/my-package",
  "version": "0.1.0",
  "type": "module",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    }
  },
  "scripts": {
    "build": "vite build",
    "typecheck": "tsc --noEmit",
    "clean": "rm -rf dist"
  }
}
  1. Add Vite config with SSR support for Node.js:
// vite.config.ts
import { resolve } from "node:path";
import { defineConfig } from "vite";
import dts from "vite-plugin-dts";

export default defineConfig({
  plugins: [dts({ rollupTypes: true })],
  build: {
    ssr: true,  // Important: Enables proper Node.js module handling
    lib: {
      entry: resolve(__dirname, "src/index.ts"),
      formats: ["es"],
    },
    target: "node20",
    sourcemap: true,
    minify: false,
    rollupOptions: {
      external: [/^node:/, /* add external deps */],
    },
  },
});
  1. Use in other packages:
pnpm add @myapp/my-package --filter @myapp/cli

🤝 Contributing

  1. Read the essential guides:
    • CLAUDE.md - Development guidelines and coding standards
    • LIBRARIES.md - Core libraries usage and troubleshooting
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Make your changes following the guidelines
  4. Run quality checks: ./scripts/check.sh
  5. Commit with clear messages
  6. Open a pull request

📚 Documentation & Support

Core Libraries

This project uses several specialized libraries. For detailed usage, troubleshooting, and best practices, see:

LIBRARIES.md - Comprehensive guide covering:

  • Optique (CLI parsing)
  • debug (Development debugging)
  • Zod (Schema validation)
  • Conf (Configuration storage)
  • envalid (Environment validation)
  • tsup (Build tool)
  • Biome (Linting/Formatting)

Finding Documentation

When working with these libraries, use these tools in order:

  1. Context7 MCP (Most accurate) - Get latest API docs

    resolve-library-id <library-name>
    get-library-docs <library-id> --topic "<topic>"
  2. Exa MCP (Code examples) - Find real-world usage

    get-code-context-exa "<library> <feature> examples"
  3. Web Search (Guides) - Find tutorials and guides

    web-search "<library> documentation"

📄 License

MIT


Built with TypeScript, Optique, and ❤️

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •