Почему 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