Утилита командной строки
Утилита командной строки (CLI) — программа, которой управляют из терминала флагами и ответами на вопросы: npm, git, node устроены именно так. Для блога напишем утилиту blog, которая добавляет статью. Флаги разберёт встроенная функция parseArgs из node:util, а недостающие данные утилита спросит через node:readline.
Разбор флагов
parseArgs() превращает массив аргументов в объект. Каждый флаг описывают: тип string (со значением) или boolean (есть или нет) и, по желанию, короткое имя:
import { parseArgs } from 'node:util';
const { values, positionals } = parseArgs({
args: ['add', '--title', 'Привет, CLI', '-d'],
options: {
title: { type: 'string', short: 't' },
draft: { type: 'boolean', short: 'd', default: false },
},
allowPositionals: true,
});
console.log(positionals); // [ 'add' ]
console.log(values.title, values.draft); // Привет, CLI truepositionals — аргументы без флага, например команда add. В настоящей программе поле args не указывают — тогда берётся process.argv.slice(2).
Неизвестный флаг
Если передать флаг, которого нет в options, parseArgs() бросит ошибку. Это защищает от опечаток:
import { parseArgs } from 'node:util';
try {
parseArgs({ args: ['--titel', 'Опечатка'], options: { title: { type: 'string' } } });
} catch (error) {
console.log(error.code); // ERR_PARSE_ARGS_UNKNOWN_OPTION
}Вопросы пользователю
node:readline/promises задаёт вопрос и ждёт ответа через await. Создайте файл bin/blog.js:
// bin/blog.js
import { parseArgs } from 'node:util';
import readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
import { addPost } from '../src/posts.js';
const { values } = parseArgs({
options: {
title: { type: 'string', short: 't' },
help: { type: 'boolean', short: 'h' },
},
});
if (values.help) {
console.log('Использование: blog --title "Заголовок"');
} else {
const rl = readline.createInterface({ input, output });
const title = values.title ?? (await rl.question('Заголовок статьи: '));
const body = await rl.question('Текст статьи: ');
rl.close();
const post = await addPost(title, body);
console.log(`Статья №${post.id} сохранена`);
}node bin/blog.js -t "Привет" спросит только текст, а без флага — и заголовок. rl.close() обязателен: без него программа будет ждать ввода и не завершится.
Запуск по имени
Чтобы вызывать утилиту словом blog, добавьте в начало bin/blog.js строку #!/usr/bin/env node, а в package.json — поле bin:
{
"bin": {
"blog": "./bin/blog.js"
}
}Команда npm link в папке проекта сделает blog доступной во всём терминале: blog --title "Новая статья".
Частая ошибка
Флаг с типом boolean не принимает значение. Запись --draft=no вызовет ошибку, а --draft no ещё коварнее: no станет отдельным позиционным аргументом (или ошибкой, если они не разрешены), а draft всё равно окажется true:
import { parseArgs } from 'node:util';
const { values, positionals } = parseArgs({
args: ['--draft', 'no'],
options: { draft: { type: 'boolean' } },
allowPositionals: true,
});
console.log(values.draft, positionals); // true [ 'no' ]Флаги со значением объявляйте с типом string, а boolean-флаг включайте самим фактом его наличия: --draft.