Lab 15: Capstone — CLI Tool

Time: 30 minutes | Level: Practitioner | Docker: docker run -it --rm node:20-alpine sh

Overview

Build a fully-featured CLI tool in Node.js: argument parsing, colored output, file operations, HTTP requests, JSON config, progress indicator, and comprehensive error handling.


Step 1: Project Setup

mkdir /app/mycli && cd /app/mycli
npm init -y
npm install minimist chalk ora
chmod +x index.js

Step 2: Argument Parsing

// lib/args.js
const minimist = require('minimist');

const COMMANDS = ['fetch', 'file', 'config', 'help'];

function parseArgs(argv = process.argv.slice(2)) {
  const args = minimist(argv, {
    string: ['output', 'format', 'config'],
    boolean: ['verbose', 'help', 'version', 'json'],
    alias: {
      h: 'help',
      v: 'verbose',
      o: 'output',
      f: 'format',
      c: 'config',
      V: 'version'
    },
    default: {
      format: 'table',
      config: '~/.myclirc.json'
    }
  });

  const command = args._[0];
  const positional = args._.slice(1);

  return { command, positional, flags: args };
}

module.exports = { parseArgs, COMMANDS };

Step 3: Colored Output


Step 4: File Operations


Step 5: HTTP Requests


Step 6: Config Management


Step 7: Progress Indicator


Step 8: Capstone — Main CLI Entry Point

Run verification (inline simulation):

📸 Verified Output:


Summary

Component
Library/Module
Purpose

Arg parsing

minimist

Parse --flags and positional args

Colored output

chalk

Terminal colors and styling

Progress

ora / custom

Spinner for async operations

File I/O

node:fs/promises

Read/write JSON config and data

HTTP

node:http/https

Fetch remote data without deps

Config

Custom + JSON file

Persist user preferences

Error handling

Custom + process.exit

Clean error messages + exit codes

Key CLI Best Practices

  • Always validate required arguments before doing work

  • Use non-zero exit codes on error (process.exit(1))

  • Check process.stdout.isTTY before using colors/spinners

  • Support --help and --version flags

  • Accept config via file + flags (flags override config)

  • Handle SIGINT for graceful exit (process.on('SIGINT', cleanup))

Last updated