CLI утилиты на TypeScript: от идеи до готового инструмента за час

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему TypeScript — идеальный язык для CLI-утилит?

Если вы пишете на Python и вдруг задумались о создании консольной утилиты, скорее всего, ваш первый порыв — взять argparse или click. И это логично. Но что, если я скажу вам, что TypeScript может дать фору любому Python-инструменту в мире CLI? Да, вы не ослышались.

TypeScript — это не просто "JavaScript с типами". Это язык, который превращает разработку CLI-утилит в предсказуемый и безопасный процесс. Никаких больше "а почему у меня undefined вместо строки?" или "я передал число, а оно стало строкой". Строгая типизация отсекает целый класс ошибок еще на этапе компиляции.

Но главное преимущество — экосистема. Node.js позволяет запускать TypeScript-код где угодно: на сервере, в Docker, на Raspberry Pi. А npm делает публикацию утилиты делом пяти минут. Сегодня мы пройдем весь путь: от пустой папки до готовой CLI-утилиты, которую можно будет установить командой npm install -g.

Что мы будем строить?

Представьте: вы часто работаете с JSON-файлами и вам нужно быстро подсчитать количество полей, проверить валидность или конвертировать в CSV. Мы напишем утилиту json-toolkit, которая делает три вещи:

  • validate — проверяет JSON на валидность;
  • count — считает количество ключей верхнего уровня;
  • to-csv — конвертирует массив объектов в CSV.

Звучит просто? Да. Но на этом примере вы освоите все ключевые приемы: обработку аргументов, работу с файловой системой, цвета в терминале и обработку ошибок.

Шаг 1. Настройка проекта

Первым делом создаем папку и инициализируем проект:

mkdir json-toolkit
cd json-toolkit
npm init -y

Теперь устанавливаем TypeScript и необходимые пакеты:

npm install -D typescript @types/node
npm install commander chalk
  • commander — библиотека для парсинга аргументов командной строки (аналог Python-аргпарса, но удобнее);
  • chalk — раскрашивает вывод в терминале;
  • @types/node — типы для Node.js.

Создаем tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "declaration": true
  },
  "include": ["src/**/*"]
}

Обратите внимание: outDir — папка, куда будет компилироваться код, а rootDir — исходники. Это стандартная практика для TypeScript-проектов.

Шаг 2. Пишем ядро утилиты

Создаем папку src и файл src/index.ts. Начнем с импортов и настройки commander:

import { Command } from 'commander';
import { readFileSync, existsSync } from 'fs';
import chalk from 'chalk';

const program = new Command();

program
  .name('json-toolkit')
  .description('Утилита для работы с JSON-файлами')
  .version('1.0.0');

program
  .command('validate')
  .argument('<file>', 'путь к JSON-файлу')
  .description('Проверить валидность JSON')
  .action((file: string) => {
    try {
      const content = readFileSync(file, 'utf-8');
      JSON.parse(content);
      console.log(chalk.green('✓ JSON валиден!'));
    } catch (err) {
      console.error(chalk.red('✗ Ошибка:'), (err as Error).message);
    }
  });

program
  .command('count')
  .argument('<file>', 'путь к JSON-файлу')
  .description('Подсчитать количество ключей верхнего уровня')
  .action((file: string) => {
    if (!existsSync(file)) {
      console.error(chalk.red('Файл не найден!'));
      return;
    }
    const content = readFileSync(file, 'utf-8');
    const data = JSON.parse(content);
    const keys = Object.keys(data);
    console.log(chalk.blue(`Количество ключей: ${keys.length}`));
  });

program
  .command('to-csv')
  .argument('<file>', 'путь к JSON-файлу')
  .option('-o, --output <file>', 'выходной CSV-файл', 'output.csv')
  .description('Конвертировать массив объектов в CSV')
  .action((file: string, options: { output: string }) => {
    const content = readFileSync(file, 'utf-8');
    const data = JSON.parse(content);
    if (!Array.isArray(data) || data.length === 0) {
      console.error(chalk.red('JSON должен быть массивом объектов!'));
      return;
    }
    const headers = Object.keys(data[0]);
    const csvRows = [headers.join(',')];
    for (const row of data) {
      const values = headers.map(header => {
        const val = row[header];
        return typeof val === 'string' ? `"${val}"` : String(val);
      });
      csvRows.push(values.join(','));
    }
    const fs = require('fs');
    fs.writeFileSync(options.output, csvRows.join('\n'), 'utf-8');
    console.log(chalk.green(`CSV сохранен в ${options.output}`));
  });

program.parse();

Разберем, что здесь происходит:

  • Commander — мы определяем три подкоманды: validate, count, to-csv.
  • Chalk — добавляем цвета: зеленый для успеха, красный для ошибок, синий для информации.
  • Обработка ошибок — проверяем существование файла, ловим исключения при парсинге JSON.

Обратите внимание на program.parse() — это запускает парсинг аргументов. Без него ничего не заработает.

Шаг 3. Добавляем "shebang" и настраиваем package.json

Чтобы утилита запускалась как json-toolkit, нужно добавить специальную строку в начало скомпилированного файла. Но мы пойдем другим путем — добавим ее в исходник:

#!/usr/bin/env node
// ... весь код выше

Теперь настроим package.json:

{
  "name": "json-toolkit",
  "version": "1.0.0",
  "description": "CLI утилита для работы с JSON",
  "main": "dist/index.js",
  "bin": {
    "json-toolkit": "./dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "ts-node src/index.ts"
  },
  "dependencies": {
    "chalk": "^4.1.2",
    "commander": "^11.0.0"
  },
  "devDependencies                

Блоги

Рекомендаций по книгам