Создание отличного интерфейса командной строки

Если вы собираетесь создать удобный инструмент разработчика, само собой разумеется, что правильный интерфейс командной строки для взаимодействия с вашим API имеет первостепенное значение. Поскольку Zeit и Heroku задают тон для этих типов инструментов разработчика, проводя обширные исследования передовых практик, когда дело доходит до опыта работы с командной строкой, мы начали наш поиск с изучения их результатов.

Поскольку Stream CLI в настоящее время находится в стадии публичного бета-тестирования, методы и философия, которые мы нашли в ходе наших исследований, а также те, которые мы обнаружили сами, свежи в наших умах. Мы хотели выделить несколько минут, чтобы обрисовать, какие, по нашему мнению, лучшие практики среди других инструментов CLI и потребности разработчиков, когда дело доходит до создания надлежащего CLI.

Ниже приводится пошаговое объяснение того, как мы будем строить еще один интерфейс командной строки, и некоторые объяснения того, почему мы решили действовать именно так, как мы.

Параметры

Появилось большое количество проектов с открытым исходным кодом, которые помогают упростить создание лесов и общую разработку интерфейса командной строки.

Помимо нашей серверной инфраструктуры здесь, в Stream, которая написана в основном на Go, мы используем JavaScript для многих наших инструментов - его гибкости между внешними и внутренними проектами, большого количества вкладов в него с открытым исходным кодом, его глобального присутствия и простота использования (по некоторым из вышеупомянутых причин) - все это делает его очевидным выбором для создания мощного инструмента с низким барьером для входа.

Точно так же, если вы отправляетесь в приключение по созданию интерфейса командной строки, появятся десятки проектов с открытым исходным кодом, созданных с использованием JavaScript, которые помогут вам начать работу. Честно говоря, когда мы начали заниматься созданием интерфейса командной строки, Commander и Corporal были в топе Google и npm почти по каждому поисковому запросу, но мы хотели чего-то более надежного - проверенного в боях проекта, который предоставлял все, что нам нужно. за один раз, а не пакет, который просто анализировал аргументы, передавал их вместе с командой.

Вот тогда мы и нашли Оклифа.

Оклиф

Oclif - это среда командной строки на основе JavaScript, исходный код которой был открыт командой разработчиков Heroku. Он поставляется с предустановленными функциями и даже предлагает расширяемость за счет использования плагинов.

С первого взгляда, когда мы изучали oclif, выделялись несколько основных функций:

  • Поддержка нескольких команд
  • Автоматический анализ аргументов или флагов команд
  • Поддержка конфигурации
  • Автоматическое документирование кодовой базы

В конечном счете, доступность этих функций также была основной причиной, по которой мы решили двигаться дальше, используя oclif в качестве основы для нашего инструмента CLI здесь, в Stream.

Помните, что это лишь некоторые из встроенных функций, с которыми Oclif поставляется из коробки. Полный список опций можно найти в официальной документации по oclif здесь.

Поддержка нескольких команд против поддержки одной команды

Важно отметить, что если у вас есть одна конечная точка или метод, который вы вызываете, поддержка одной команды (например, grep) - это все, что вам нужно. Если вы разрабатываете более крупный инструмент CLI, такой как тот, который мы создали для Stream, вам, вероятно, потребуется включить поддержку нескольких команд (например, npm или git). Вот краткое описание различий:

Одинокий:

$ stream --api_key=foo --api_secret=bar --name=baz --email=qux

Мульти:

$ stream config:set --api_key=foo --api_secret=bar --name=baz --email=qux

Хотя они могут выглядеть одинаково, между двумя вариантами есть одно ключевое различие: одна команда не позволяет использовать подкоманды или область видимости, как мы любим это называть. Это просто означает, что сложные или вложенные команды невозможны при поддержке одной команды.

Команды обоих типов принимают аргументы независимо от конфигурации. Без аргументов это не был бы интерфейс командной строки. Одним из преимуществ поддержки нескольких команд является то, что он разделяет вызовы двоеточием, что позволяет упорядочить работу. Более того, вы можете организовать структуру каталогов, используя вложенные каталоги, как показано в коде src на GitHub.

Иногда вначале бывает сложно осмыслить концепцию, однако, как только вы запачкаете руки, создавая интерфейс командной строки в первый раз, все соберется воедино и обретет смысл.

Автоматический синтаксический анализ

Под капотом oclif обрабатывает переданные аргументы командной строки. Как правило, с Node.js вам придется извлекать аргументы из массива, предоставленного process.argv. Хотя это не особенно сложно, это определенно подвержено ошибкам… особенно когда вы добавляете требования для проверки или преобразования в строки / логические значения.

Если вы не планируете использовать oclif для обработки синтаксического анализа за вас и вам просто нужно продвинуться вперед с простой настройкой, мы рекомендуем minimist, пакет, предназначенный для синтаксического анализа аргументов в командной строке.

Поддержка конфигурации

При любой интеграции на стороне сервера, будь то API или SDK, вам (надеюсь), вероятно, придется предоставить какой-либо токен (по соображениям безопасности и идентификации).

Для нашей интеграции нам нужно было сохранить учетные данные конфигурации для пользователя (например, ключ и секрет API, имя и адрес электронной почты) в безопасном месте на компьютере пользователя. Без сохранения этого типа данных мы должны были бы убедиться, что каждый вызов API для Stream включал правильные учетные данные и, давайте признаем, никто не хочет передавать аргументы с каждой командой.

Чтобы обойти эту проблему, мы используем встроенную поддержку oclif для управления файлами конфигурации, сохраняя учетные данные пользователя в config.js файле в каталоге config на их компьютере. Обычно каталог конфигурации находится в ~/.config/stream-cli на машинах Unix или %LOCALAPPDATA%\stream-cli на машинах Windows. С помощью oclif нам не нужно беспокоиться об обнаружении типа машины пользователя, поскольку они заботятся об этом различии под капотом, что упрощает доступ к классу вашей команды с помощью this.config.configDir.

Зная это, мы смогли создать небольшую утилиту для сбора и хранения необходимых учетных данных с помощью пакета fs-extra. Посмотрите код здесь.

Документацию по параметрам конфигурации в Oclif можно найти здесь.

Автоматическое документирование кодовой базы

Мы были очень счастливы (и удивлены), обнаружив, что oclif поддерживает команды автодокументирования. Без такой функциональности нам пришлось бы вручную изменять наш README и базовые документы каждый раз, когда мы вносили изменения, такие как добавление / удаление аргумента команды, изменение имени команды или изменение структуры каталогов в нашем подкаталоге команд. Вы, наверное, можете себе представить, насколько сложно было бы поддерживать это в большом проекте CLI, таком как Stream CLI.

С помощью пакета @ oclif / dev-cli мы смогли добавить единственный скрипт в наш файл package.json, который запускается в процессе сборки. Команда сканирует структуру каталогов и волшебным образом генерирует документы, как показано здесь.

Поддержка интерактивных и необработанных аргументов

Иногда при вызове команды через инструмент командной строки одной из последних вещей, которые могут занять место в вашем мозгу, являются все необходимые аргументы для этой команды, особенно если их много. Хотя вы всегда можете использовать флаг - help для распечатки необходимых аргументов, иногда лучше предоставить интерактивную подсказку, которая запрашивает у пользователя различную информацию, если она отсутствует в предоставленных флагах.

Например, вместо звонка:

$ stream config:set --api_key=foo --api_secret=bar --name=baz --email=qux

Пользователь может вызвать (без переданных аргументов):

$ stream config:set

И им будет предложено это:

Есть несколько вариантов подсказок пользователям, и мы обнаружили, что Enquirer - самый простой пакет для работы. Хотя этот пакет аналогичен по функциональности Inquirer, Enquirer API имеет тенденцию быть немного более снисходительным и более простым в работе.

По возможности важно попытаться применить эту функциональность стиля подсказки ко всем командам с несколькими аргументами. Однако не забудьте проверить флажки, чтобы убедиться, что вы не запрашиваете информацию у пользователя, если он ее уже передал. Например:

if (!flags.name || !flags.email || !flags.key || !flags.secret) {
    const res = await prompt([
        {
            type: 'input',
            name: 'name',
            message: `What is your full name?`,
            required: true,
        },
        {
            type: 'input',
            name: 'email',
            message: `What is your email address associated with Stream?`,
            required: true,
        },
        {
            type: 'input',
            name: 'key',
            message: `What is your Stream API key?`,
            required: true,
        },
        {
            type: 'password',
            name: 'secret',
            message: `What is your Stream API secret?`,
            required: true,
        },
    ]);

    for (const key in res) {
        if (res.hasOwnProperty(key)) {
            flags[key] = res[key];
        }
    }
}

Обратите внимание, как мы проверяем флаги и отображаем подсказку ТОЛЬКО в том случае, если флаги не существуют.

Сделай это красиво

Командные строки обычно представляют собой мягкий зеленый и белый текст на черном фоне. Новости: на самом деле ничто не мешает вам выделить свой интерфейс командной строки. На самом деле, разработчикам нравится, когда в командную строку вводятся цвета - цвета помогают отличить ошибки от сообщений об успехе, событий / меток времени и т. Д.

Если вы хотите сделать вещи красивыми, Chalk - отличный, если не лучший, пакет для использования. Он предоставляет обширный API для добавления цветов в ваш интерфейс командной строки с минимальными накладными расходами или без них.

Чтобы интегрировать Chalk в ваш интерфейс командной строки:

import chalk from ‘chalk’;

Затем оберните строку методом мелка, цветом и дополнительным стилем (полужирным шрифтом, курсивом и т. Д.), Чтобы добавить немного изюминки к вашему выводу:

this.log(`This is a response and it’s ${chalk.blue.bold.italic(‘bold, blue, and italicized’)}`);

Используйте таблицы для больших ответов

Посмотрим правде в глаза, ни один разработчик не захочет разбираться с большим ответом, возвращаемым вашим API. В этом случае важно всегда возвращать что-то значимое и легко читаемое. Один из наших любимых способов предоставить пользователю легко усваиваемый вывод - использовать таблицу:

В приведенном выше примере мы выбрали пакет cli-table, чтобы помочь отображать данные в таблицах, поскольку он предоставляет простой в использовании и гибкий API, который поддерживает следующее:

  • Вертикальные и горизонтальные дисплеи
  • Поддержка цвета текста / фона
  • Выравнивание текста (слева, по центру, справа) с отступом
  • Поддержка настраиваемой ширины столбца
  • Автоматическое усечение на основе заданной ширины

Печать JSON для анализа с помощью Bash и jq

Прелесть предоставления CLI заключается в том, что он может быть вызван либо пользователем, либо скриптом. Часть создания легкодоступного и удобного инструмента по умолчанию заключается в общении, которое сразу становится понятным для пользователя. С учетом сказанного, создание сценариев позволяет использовать метод невмешательства, что особенно полезно, когда пользователь хочет выполнить набор команд, а не запускать разовые команды.

Хотя Stream CLI по умолчанию возвращает удобные для пользователя (и понятные для человека) выходные данные (см. «Сделайте это красивым» и «Используйте таблицы для больших ответов»), мы понимаем, что при запуске сценария вам, скорее всего, потребуется подробный ответ вместо человеческих. читаемое сообщение. Чтобы получить доступ к необработанным данным ответа, мы добавили флаг --json, который позволяет пользователю указать необработанные полезные данные как JSON для вывода ответа.

Ниже приведен быстрый пример, показывающий, как получить учетные данные для пользователя из Stream CLI, направив вывод непосредственно в jq, легкий и гибкий процессор JSON командной строки:

#! /bin/bash

run=$(stream config:get --json)

name=$(jq --raw-output '.name' <<< "${run}")
email=$(jq --raw-output '.email' <<< "${run}")
apiKey=$(jq --raw-output '.apiKey' <<< "${run}")
apiSecret=$(jq --raw-output '.apiSecret' <<< "${run}")

echo $name
echo $email
echo $apiKey
echo $apiSecret

Мы обнаружили, что предоставление этой функции особенно полезно для Stream Chat, если пользователь хочет настроить свою инфраструктуру чата, предоставить пользователей, разрешения и т. Д. За один раз без использования базового REST API.

Издательский

Публикация CLI может показаться сложной, но это ничем не отличается от публикации любого другого пакета на npm. Основные шаги следующие:

  1. Обновите файл oclif.manifest.json с помощью инструментов из пакета @ oclif / dev-cli. Этот файл сканирует каталог и обновляет файл манифеста, используя обновленную версию интерфейса командной строки, а также все команды, доступные пользователю. Затем файл манифеста можно обновить, вызвав rm -f oclif.manifest.json && oclif-dev manifest из командной строки.
  2. Обновите документы, чтобы отразить любые изменения, внесенные в команды. Это также инструмент, предоставляемый пакетом @ oclif / dev-cli, и его можно запустить с помощью oclif-dev readme --multi (или --single, если вы используете интерфейс командной строки с одной командой).
  3. Поднимите версию npm с помощью команды version (например, npm version prerelease). Полную документацию по команде npm version можно найти здесь.
  4. Опубликуйте выпуск в npm с помощью команды npm publish.

Затем пользователь может установить CLI глобально с помощью npm или yarn:

npm -g install <YOUR_CLI_PACKAGE>

Or:

yarn global add <YOUR_CLI_PACKAGE>

Если вам необходимо распространить ваш интерфейс командной строки в виде архива, мы рекомендуем использовать команду oclif-dev pack, предоставляемую пакетом @ oclif / dev-cli. Эта команда позволит вам развертывать пакеты в Homebrew и других менеджерах пакетов для конкретных ОС или просто запускать их независимо в системе.

Ключевые выводы

Если вы хотите вникнуть в полный исходный код Stream CLI, вы можете найти репозиторий GitHub с открытым исходным кодом здесь. Хотя основные выводы в этом посте не являются исчерпывающим списком наших рекомендаций по передовому опыту, мы надеемся, что вы уйдете из этого поста с некоторыми дополнительными знаниями, которые можно применить к вашему интерфейсу командной строки. Подведем итог нашим основным выводам из этого начинания:

  • Для вдохновения взгляните на функциональность, которую Zeit и Heroku предоставляют в своем интерфейсе командной строки, чтобы создать потрясающий опыт командной строки разработчика.
  • Если вашему API / CLI требуется постоянство данных, сохраните эти данные в каталоге кэша, специфичном для вашего CLI. Загрузите это, используя файл утилиты, как мы делаем в Stream. Также обратите внимание, что пакет fs-extra пригодится для этого типа вещей (хотя поддержка встроена в oclif).
  • Oclif - это правильный выбор, особенно если вы создаете большой интерфейс командной строки, а не интерфейс командной строки с одной командой. Если вы создаете интерфейс командной строки с одной командой, вы все равно можете использовать oclif; просто убедитесь, что вы указали, что это API с одной командой, когда вы строите свой интерфейс командной строки.
  • Не хотите использовать фреймворк? Это нормально! Пакет minimist обеспечивает анализ аргументов в командной строке и может быть легко использован в вашем проекте.
  • По возможности используйте подсказки с Enquirer или другим пакетом по вашему выбору. Пользователи должны пройти через требования команды и запросить данные, необходимые команде для правильного выполнения. Обратите внимание, что это никогда не должно требоваться (например, позвольте пользователю обойти приглашение, если он передаст правильные аргументы).
  • По возможности используйте цвета, чтобы интерфейс командной строки был удобнее для глаз. Мел - отличный инструмент для этого.
  • Если у вас есть данные ответа, которые достаточно хорошо структурированы, не просто распечатывайте их для пользователя (если это не то, что они указали). Вместо этого поместите его в таблицу с помощью cli-table.
  • Всегда позволяйте пользователю указывать тип вывода (например, JSON), но по умолчанию используется сообщение, читаемое человеком.
  • Держи это быстро! Для трудоемких задач, таких как загрузка файлов или выполнение команд, требующих нескольких вызовов API, мы рекомендуем показывать индикатор загрузки, чтобы пользователь знал, что работа выполняется в фоновом режиме. Если вы ищете пакет на npm, рекомендуем попробовать ora.

Как всегда, мы также будем рады услышать ваши мысли и мнения, поэтому, пожалуйста, оставляйте их в комментариях ниже!

Если вы заинтересованы в создании продукта для чата на платформе Stream, мы рекомендуем пройти через наше интерактивное руководство. Полную документацию по Stream Chat API вы можете просмотреть здесь.