Back to KB
Difficulty
Intermediate
Read Time
6 min

Building a CLI Tool with Node.js: From Zero to npm

By Codcompass TeamΒ·Β·6 min read

A step-by-step guide to building, testing, and publishing a command-line tool.

Why Build a CLI?

  • Solves YOUR problem (automate repetitive tasks)
  • Portfolio piece (shows you can ship complete tools)
  • Potential income (people pay for useful CLIs)
  • Fun to build (instant gratification β€” no UI needed)

What We'll Build

A jsonfmt tool that formats JSON files in place:

$ cat messy.json
{"name":"Alex","age":30,"skills":["js","python"]}

$ jsonfmt messy.json

$ cat messy.json
{
  "name": "Alex",
  "age": 30,
  "skills": [
    "js",
    "python"
  ]
}

Enter fullscreen mode Exit fullscreen mode

Step 1: Project Setup

mkdir jsonfmt && cd jsonfmt
npm init -y

# Install dependencies
npm install commander chalk fs-extra

# Install dev dependencies
npm install -D jest typescript @types/node tsup

Enter fullscreen mode Exit fullscreen mode

// package.json
{
  "name": "jsonfmt",
  "version": "1.0.0",
  "description": "Format JSON files beautifully",
  "type": "module",           // ESM imports!
  "bin": {
    "jsonfmt": "./dist/cli.js"
  },
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  },
  "files": ["dist"],
  "scripts": {
    "build": "tsup src/index.ts src/cli.js --format esm --dts",
    "dev": "node --watch src/cli.js",
    "test": "jest"
  },
  "engines": { "node": ">=16" },
  "license": "MIT"
}

Enter fullscreen mode Exit fullscreen mode

Step 2: Core Logic

// src/index.ts
import { readFile, writeFile } from 'fs-extra';
import chalk from 'chalk';

export interface FormatOptions {
  indent?: number;
  sort?: boolean;
  validate?: boolean;
}

const DEFAULTS: Required<FormatOptions> = {
  indent: 2,
  sort: false,
  validate: true,
};

/**
 * Format a JSON string with options.
 */
export function formatJson(text: string, options: FormatOptions = {}): string {
  const opts = { ...DEFAULTS, ...options };

  let parsed = JSON.parse(text);

  if (opts.sort) {
    parsed = sortObject(parsed);
  }

  return JSON.stringify(parsed, null, opts.indent) + '\n';
}

/**
 * Format a JSON file in-place.
 */
export async function formatFile(
  filePath: string, 
  options: FormatOptions = {}
): Promise<{ before: number; after: number }> {
  const content = await readFile(filePath, 'utf8');
  const formatted = formatJson(content, options);

  await writeFile(filePath, formatted, 'utf8');

  return { 
    before: content.length, 
    after: formatted.length 
  };
}

/** Recursively sort object keys */
function sortObject(obj: unknown): unknown {
  if (Array.

πŸŽ‰ Mid-Year Sale β€” Unlock Full Article

Base plan from just $4.99/mo or $49/yr

Sign in to read the full article and unlock all 635+ tutorials.

Sign In / Register β€” Start Free Trial

7-day free trial Β· Cancel anytime Β· 30-day money-back