DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Building CLI Tools With Node.js: From Script to npm Package

Building CLI Tools With Node.js: From Script to npm Package

The best CLI tools are ones that scratch your own itch — a script you'd write anyway, packaged for others.

Project Setup

mkdir my-cli && cd my-cli
npm init -y
npm install commander chalk ora
npm install -D typescript @types/node tsx
Enter fullscreen mode Exit fullscreen mode
// package.json
{
  "name": "my-cli",
  "bin": { "my-cli": "./dist/index.js" },
  "scripts": {
    "build": "tsc",
    "dev": "tsx src/index.ts"
  }
}
Enter fullscreen mode Exit fullscreen mode

Main Entry Point

#!/usr/bin/env node
import { Command } from 'commander';
import chalk from 'chalk';
import ora from 'ora';

const program = new Command();

program
  .name('my-cli')
  .description('A useful CLI tool')
  .version('1.0.0');

program
  .command('generate <type>')
  .description('Generate a new component')
  .option('-d, --dir <directory>', 'output directory', './src')
  .option('--no-test', 'skip generating test file')
  .action(async (type, options) => {
    const spinner = ora(`Generating ${type}...`).start();

    try {
      await generateComponent(type, options);
      spinner.succeed(chalk.green(`Created ${type} successfully`));
    } catch (err) {
      spinner.fail(chalk.red(`Failed: ${(err as Error).message}`));
      process.exit(1);
    }
  });

program.parse();
Enter fullscreen mode Exit fullscreen mode

Interactive Prompts

import { input, select, confirm } from '@inquirer/prompts';

async function interactiveSetup() {
  const name = await input({ message: 'Project name:' });

  const framework = await select({
    message: 'Choose framework:',
    choices: [
      { value: 'nextjs', name: 'Next.js' },
      { value: 'remix', name: 'Remix' },
      { value: 'astro', name: 'Astro' },
    ],
  });

  const includeStripe = await confirm({ message: 'Include Stripe billing?' });

  return { name, framework, includeStripe };
}
Enter fullscreen mode Exit fullscreen mode

Publishing to npm

# Build
npm run build

# Make the entry point executable
chmod +x dist/index.js

# Test locally
npm link
my-cli generate component

# Publish
npm publish --access public
Enter fullscreen mode Exit fullscreen mode

Exit Codes

// Always exit with proper codes
process.exit(0); // Success
process.exit(1); // General error
process.exit(2); // Misuse of CLI

// Handle unhandled rejections
process.on('unhandledRejection', (err) => {
  console.error(chalk.red('Unhandled error:'), err);
  process.exit(1);
});
Enter fullscreen mode Exit fullscreen mode

CLI tools, code generators, and automation scripts are part of the developer tooling patterns in the Ship Fast Skill Pack — including a /generate skill that scaffolds components from your project's conventions.

Top comments (0)