npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@trailhead/cli

v1.0.1

Published

Focused CLI framework orchestrator for building production-ready command-line applications with TypeScript and Result types

Downloads

22

Readme

@trailhead/cli

Functional CLI framework for building production-ready command-line applications with TypeScript

TypeScript Node License

Modern CLI framework built with functional programming principles, explicit Result-based error handling, and comprehensive testing utilities. No exceptions, no classes—just pure functions and immutable data.

Why Choose @trailhead/cli?

🎯 Explicit Error Handling

Uses Result types instead of exceptions. Every error path is explicit at compile time, making your CLI applications more reliable and easier to debug.

🧪 Testing First

Comprehensive testing utilities built-in with 50% boilerplate reduction. Mocks, assertions, and test contexts for CLI applications.

Performance Optimized

Caching systems, streaming APIs, and optimized command processing for production workloads.

🔧 Functional Design

Pure functions, immutable data, and composition patterns throughout. No classes, no side effects, just predictable behavior.

Quick Start

Installation

# Install from npm
pnpm add @trailhead/cli

# Or generate a new project
npx @trailhead/create-cli my-cli

Quick Example

import { createCommand } from '@trailhead/cli/command'
import { ok } from '@trailhead/core'

const greetCommand = createCommand({
  name: 'greet',
  description: 'Greet someone',
  options: {
    name: {
      type: 'string',
      description: 'Name to greet',
      required: true,
    },
  },
  action: async ({ name }) => {
    console.log(`Hello, ${name}!`)
    return ok(undefined)
  },
})

// Run your command
await greetCommand.execute(['--name', 'World'])

Key Features

  • Result-based error handling - Explicit error paths with Result types instead of exceptions
  • Functional programming - Pure functions, immutable data, composition patterns
  • Testing utilities - Built-in mocks, assertions, and test contexts
  • Performance optimized - Caching, streaming APIs, and optimized command processing
  • Type-safe - Full TypeScript support with strict type checking

Core Concepts

Result Types - No Exceptions

import { Result, ok, err } from '@trailhead/core'

// Functions return Results instead of throwing
const deployApp = async (env: string): Promise<Result<string, Error>> => {
  if (!env) {
    return err(new Error('Environment required'))
  }

  // Simulate deployment
  if (env === 'production') {
    return ok('Deployed to production successfully')
  }

  return err(new Error(`Unknown environment: ${env}`))
}

// Handle results explicitly
const result = await deployApp('staging')
if (result.isOk()) {
  console.log(result.value)
} else {
  console.error('Deploy failed:', result.error.message)
}

Command Composition

import { createCommand, executeWithPhases } from '@trailhead/cli/command'

const buildCommand = createCommand({
  name: 'build',
  description: 'Build and deploy application',
  action: async (options, context) => {
    // Multi-phase execution with progress tracking
    return executeWithPhases(
      [
        {
          name: 'validate',
          action: async () => validateProject(context),
        },
        {
          name: 'build',
          action: async () => buildProject(context),
        },
        {
          name: 'test',
          action: async () => runTests(context),
        },
        {
          name: 'deploy',
          action: async () => deployProject(options.env, context),
        },
      ],
      {},
      context
    )
  },
})

Module Reference

Main Export (@trailhead/cli)

The main export provides CLI creation and basic Result types:

import { createCLI, ok, err } from '@trailhead/cli'
import type { Result, CoreError } from '@trailhead/cli'

// Create a CLI application
const cli = createCLI({
  name: 'my-app',
  version: '1.0.0',
  commands: [
    /* your commands */
  ],
})

Note: For extended Result utilities, use @trailhead/core directly.

Command (@trailhead/cli/command)

Command creation, validation, and execution patterns

import {
  createCommand,
  executeWithPhases,
  executeWithValidation,
  executeWithDryRun,
} from '@trailhead/cli/command'

// Advanced command with validation
const processCommand = createCommand({
  name: 'process',
  validation: {
    inputFile: (value) => (fs.existsSync(value) ? ok(value) : err(new Error('File not found'))),
    outputDir: (value) =>
      value.length > 0 ? ok(value) : err(new Error('Output directory required')),
  },
  action: async (options, context) => {
    return executeWithDryRun(
      async () => {
        // Process files
        return processFiles(options.inputFile, options.outputDir)
      },
      options.dryRun,
      context
    )
  },
})

Testing (@trailhead/cli/testing)

Comprehensive testing utilities with mocks and assertions

import {
  createTestContext,
  createMockFileSystem,
  expectSuccess,
  expectError,
} from '@trailhead/cli/testing'

describe('my command', () => {
  test('should process files successfully', async () => {
    const mockFs = createMockFileSystem({
      '/input.txt': 'test content',
      '/output/': null, // directory
    })

    const context = createTestContext({ fileSystem: mockFs })
    const result = await myCommand.execute(['--input', '/input.txt'], context)

    expectSuccess(result)
    expect(mockFs.readFile('/output/processed.txt')).toBeDefined()
  })
})

Progress (@trailhead/cli/progress)

Progress tracking with enhanced capabilities

import { createProgressTracker } from '@trailhead/cli/progress'

const progress = createProgressTracker({
  total: 100,
  format: 'Processing [{bar}] {percentage}% | ETA: {eta}s',
})

// Use with async operations
for (let i = 0; i < 100; i++) {
  await processItem(i)
  progress.increment()
}

Utils (@trailhead/cli/utils)

Utilities for styling, package detection, and more

import { chalk, createSpinner, detectPackageManager, createLogger } from '@trailhead/cli/utils'

// Rich terminal output
console.log(chalk.success('✓ Build completed'))
console.log(chalk.error('✗ Deploy failed'))

// Spinners for long operations
const spinner = createSpinner('Deploying...')
spinner.start()
await deploy()
spinner.stop('Deployed successfully')

Advanced Features

Multi-Phase Execution

For complex workflows that need progress tracking:

import { executeWithPhases } from '@trailhead/cli/command'

const phases = [
  {
    name: 'setup',
    weight: 10,
    action: async (data, context) => setupEnvironment(context),
  },
  {
    name: 'build',
    weight: 60,
    action: async (data, context) => buildProject(context),
  },
  {
    name: 'deploy',
    weight: 30,
    action: async (data, context) => deployProject(data.env, context),
  },
]

const result = await executeWithPhases(phases, { env: 'production' }, context)

Interactive Prompts

import { createInteractiveCommand } from '@trailhead/cli/command'

const setupCommand = createInteractiveCommand({
  name: 'setup',
  prompts: async (options) => {
    const name = await input('Project name:', { default: 'my-project' })
    const framework = await select('Framework:', {
      choices: ['react', 'vue', 'svelte'],
    })
    return { name, framework }
  },
  action: async (answers, context) => {
    return generateProject(answers, context)
  },
})

File System Operations

import { fs } from '@trailhead/fs'

// Use @trailhead/fs package for file operations

// All operations return Results
const readResult = await fs.readFile('config.json')
if (readResult.isOk()) {
  const config = JSON.parse(readResult.value)
  console.log('Config loaded:', config)
} else {
  console.error('Failed to read config:', readResult.error.message)
}

// File operations with Result types
const copyResult = await fs.copy('src/template.tsx', 'dist/template.tsx')
if (copyResult.isErr()) {
  console.error('Copy failed:', copyResult.error.message)
}

Testing Best Practices

High-ROI Tests

Focus on testing business logic and user interactions:

import { createTestRunner } from '@trailhead/cli/testing'

describe('build command', () => {
  const testRunner = createTestRunner()

  test('builds project successfully', async () => {
    const result = await testRunner.runCommand('build', ['--env', 'production'])

    expect(result.exitCode).toBe(0)
    expect(result.stdout).toContain('Build completed')
    expect(result.files).toInclude('dist/index.js')
  })

  test('fails with invalid environment', async () => {
    const result = await testRunner.runCommand('build', ['--env', 'invalid'])

    expect(result.exitCode).toBe(1)
    expect(result.stderr).toContain('Unknown environment')
  })
})

Mock File System

import { createMockFileSystem } from '@trailhead/cli/testing'

test('processes configuration file', async () => {
  const mockFs = createMockFileSystem({
    '/project/config.json': JSON.stringify({ name: 'test-project' }),
    '/project/src/': null, // directory
    '/project/src/index.ts': 'export default "hello";',
  })

  const context = createTestContext({ fileSystem: mockFs })
  const result = await processProject('/project', context)

  expect(result.isOk()).toBe(true)
  expect(mockFs.exists('/project/dist/index.js')).toBe(true)
})

Documentation

API Reference

Guides & Tutorials

How-To Guides

License

MIT © Esteban URL