Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Утилита командной строки

Утилита командной строки (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 true

positionals — аргументы без флага, например команда 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.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.