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 Trial7-day free trial Β· Cancel anytime Β· 30-day money-back
