Timber.io.
Командная строка – это пользовательский интерфейс, который не уделяет достаточно внимания в мире разработки JavaScript. Реальность заключается в том, что большинство разработчиков DEV должны использовать CLI, чтобы использовать подобные Nerds, как мы, и пользовательский опыт должен быть наравне с вашим творческим созданным веб-приложением. Это включает в себя хороший дизайн, полезные меню, чистые сообщения об ошибках и выходы, индикаторы загрузки и стержня прогресса и так далее.
Там не так много реальных учебных пособий, когда речь идет о строительстве интерфейсов командной строки с узлом, так что это первая серия, которая выйдет за пределы базового приложения CLI «Hello World». Мы будем создавать приложение под названием снаружи-CLI , что даст вам нынешнюю погоду и 10-дневный прогноз на любое место.
Примечание : Есть несколько библиотек, которые помогают создавать сложные CLI, такие как oclif , Ярги и командир С Но мы будем держать наши зависимости тонкими ради этого примера, чтобы вы могли лучше понять, как дела работают под капотом. Это руководство предполагает, что у вас есть базовые рабочие знания JavaScript и узел.
Начиная
Как и во всех проектах JavaScript, создание Package.json и файл входа – лучший способ выключить его. Мы можем держать его простым – никакие зависимости еще не требуются.
{
"name": "outside-cli",
"version": "1.0.0",
"license": "MIT",
"scripts": {},
"devDependencies": {},
"dependencies": {}
}module.exports = () => {
console.log('Welcome to the outside!')
}Нам понадобится способ призвать наше недавно чешевое приложение и показать приветственное сообщение, а также добавить его на системный путь, чтобы его можно назвать из любой точки. Файл Bin – это способ сделать это.
#!/usr/bin/env node
require('../')()Никогда не видела #!/usr/bin/env node до? Это называется Shebang Отказ Он в основном говорит системе, что это не скрипт оболочки, и он должен использовать другой интерпретатор.
Важно сохранить двоичный файл Slim, так как только цель – принять приложение. Весь наш код должен жить за пределами двоичного, поэтому он может оставаться модульным и реальным. Это также поможет, если мы захотим предоставить программный доступ к нашей библиотеке в будущем.
Чтобы напрямую запускать файл BIN, нам нужно будет дать ему правильные разрешения файловой системы. Если вы на Unix, это так просто, как работает chmod + x bin/снаружи Отказ Если вы находитесь в Windows, сделайте себе одолжение и используйте подсистему Linux.
Далее мы добавим наш двоичный файл в файл Package.json. Это автоматически размещает его на системный путь пользователя, когда они устанавливают наш пакет в качестве глобального ( NPM Install instate -G вне-CLI ).
{
"name": "outside-cli",
"version": "1.0.0",
"license": "MIT",
"bin": {
"outside": "bin/outside"
},
"scripts": {},
"devDependencies": {},
"dependencies": {}
}Теперь мы можем назвать наш файл BIN напрямую, запустив ./bin/ootside Отказ Вы должны увидеть приветственное сообщение. Бег NPM ссылка В корне вашего проекта будет SymLink ваш двоичный файл на системный путь, что делает его доступным из любой точки работает снаружи Отказ
Когда вы запускаете приложение CLI, он состоит из аргументов и команд. Аргументы (или «флаги») являются ценностями, предложенные одним или двумя дефисами (например, -D , --debug или - Денв производство ) И полезно для передачи вариантов нашему приложению. Команды – все остальные значения, которые не имеют флага.
В отличие от команд, аргументы не должны быть указаны в каком-либо конкретном порядке. Например, мы могли бы запустить снаружи сегодня Бруклин И просто предположим, что вторая команда всегда будет местоположением – но не было бы лучше запустить снаружи сегодня --локация Бруклин Если мы хотим добавить больше вариантов в будущем?
Для того, чтобы наше приложение было бы полезным вообще, нам нужно разобрать эти команды и аргументы и превратить их в объект. Мы всегда могли бы прыгать в Process.argv И попробуйте сделать это сами, но давайте установим нашу первую зависимость под названием Минимист позаботиться об этом для нас.
npm install --save minimist
const minimist = require('minimist')
module.exports = () => {
const args = minimist(process.argv.slice(2))
console.log(args)
}Примечание : Причина, по которой мы удаляем первые два аргумента с .SLICE (2) Это потому, что первый ARG всегда будет переводчиком, за которым следует имя интерпретации файла. Мы только заботимся о аргументах после этого.
Сейчас работает снаружи сегодня должен выводить {_: ['сегодня']} Отказ Если вы запустите снаружи сегодня --локация "Бруклин, Нью-Йорк" это должно выводить {_: ['сегодня'], расположение: «Бруклин, Нью-Йорк»} Отказ Мы пойдем более подробно с аргументами позже, когда мы фактически используем местоположение, но на данный момент этого достаточно, чтобы настроить нашу первую команду.
Синтаксис аргументов
Чтобы лучше понять, как работает аргумент синтаксиса, Вы можете прочитать это Отказ В основном, флаг может быть односторонним или двойным зафиксированным, и примет значение, немедленно следующую в команде или будет равна истинно, когда нет значения. Флаги с одним дефишем также могут быть объединены для недолгих логировцев ( -A -B -C или -ABC предоставит вам {A: True, B: True, C: True} .)
Важно помнить, что Значения должны быть указаны, если они содержат специальные символы или пространство Отказ Бег --foo Bar Baz дал бы вам `{: [‘BAZ’], foo: ‘Bar’} , но работает –foo “Бар Баз” дал бы тебе {foo: ‘bar baz’} `. _
Это хорошая идея, чтобы разделить код для каждой команды и загружать его в память, когда она называется. Это создает более быстрое время запуска и предотвращает загрузку ненужных модулей. Достаточно легко с оператором коммутатора на главной команде, данной нам минимистом. Используя эту настройку, каждый файл команды должен экспортировать функцию, и в этом случае мы передаем аргументы каждой команде, поэтому мы можем использовать их позже.
const minimist = require('minimist')
module.exports = () => {
const args = minimist(process.argv.slice(2))
const cmd = args._[0]
switch (cmd) {
case 'today':
require('./cmds/today')(args)
break
default:
console.error(`"${cmd}" is not a valid command!`)
break
}
}module.exports = (args) => {
console.log('today is sunny')
}Теперь, если вы запустите снаружи сегодня Вы увидите сообщение «Сегодня солнечно», и если вы запустите снаружи foobar Он скажет вам, что «FOOBAR» не является действительной командой. Нам все еще нужно запросить погоду API, чтобы получить реальные данные, но это хорошее начало.
Есть несколько команд и аргументов, которые ожидают, что будут в каждом CLI: Помогите , --help и -h , что должно показать помощь меню, а версия , --version и -В который должен выводить текущую версию приложения. Мы также должны по умолчанию для основного меню справки, если команда не указана.
Это может быть легко реализовано в нашей текущей настройке, добавив два случая нашего оператора выключателя, значение по умолчанию для CMD Переменная и реализация некоторых утверждений для справки и версии аргументы флаги. Минимист автоматически анализирует аргументы к ключам/значениям, так что работает снаружи --version сделаю args.version равно правда.
const minimist = require('minimist')
module.exports = () => {
const args = minimist(process.argv.slice(2))
let cmd = args._[0] || 'help'
if (args.version || args.v) {
cmd = 'version'
}
if (args.help || args.h) {
cmd = 'help'
}
switch (cmd) {
case 'today':
require('./cmds/today')(args)
break
case 'version':
require('./cmds/version')(args)
break
case 'help':
require('./cmds/help')(args)
break
default:
console.error(`"${cmd}" is not a valid command!`)
break
}
}Для реализации наших новых команд, следуйте тому же формату, что и Сегодня команда.
const { version } = require('../package.json')
module.exports = (args) => {
console.log(`v${version}`)
}const menus = {
main: `
outside [command]
today .............. show weather for today
version ............ show package version
help ............... show help menu for a command`,
today: `
outside today
--location, -l ..... the location to use`,
}
module.exports = (args) => {
const subCmd = args._[0] === 'help'
? args._[1]
: args._[0]
console.log(menus[subCmd] || menus.main)
} Теперь, если вы запустите Средняя помощь сегодня или снаружи сегодня -h , вы должны увидеть меню справки для Сегодня команда. Бег снаружи или снаружи -h следует показать вам главное меню справки.
Эта настройка проекта действительно потрясающая, потому что если вам нужно добавить новую команду, все, что вам нужно сделать, это создать новый файл в CMDS. Папка, добавьте ее в оператор выключателя и добавьте меню справки, если он имеет один.
module.exports = (args) => {
console.log('tomorrow is rainy')
}// ...
case 'forecast':
require('./cmds/forecast')(args)
break
// ...const menus = {
main: `
outside [command]
today .............. show weather for today
forecast ........... show 10-day weather forecast
version ............ show package version
help ............... show help menu for a command`,
today: `
outside today
--location, -l ..... the location to use`,
forecast: `
outside forecast
--location, -l ..... the location to use`,
}
// ... Иногда команда может занять много времени для бега. Если вы выбираете данные из API, генерируя содержимое, написание файлов на диск или любой другой процесс, который занимает более нескольких миллисекунд, вы хотите дать пользователю некоторые отзывы, что ваше приложение не заморожено и просто работает жесткий. Иногда вы можете измерить прогресс вашей операции, и имеет смысл отображать панель прогресса, но в других случаях это более переменная и имеет смысл показывать индикатор загрузки.
Для нашего приложения мы не можем измерить прогресс наших запросов API, поэтому мы будем использовать базовый спиннер, чтобы показать что-то происходит. Установите еще два зависимости для наших сетевых запросов и нашего спиннера:
npm install --save axios ora
Получение данных из API
Теперь давайте создадим утилиту, которая сделает запрос на API Yahoo Feamone для текущих условий и прогноза местоположения.
Примечание : API Yahoo использует синтаксис «YQL», и это немного в Funky – не пытайтесь его понять, просто скопировать и вставить. Это была единственная погода API, которую я мог найти, что не требует ключа API.
const axios = require('axios')
module.exports = async (location) => {
const results = await axios({
method: 'get',
url: 'https://query.yahooapis.com/v1/public/yql',
params: {
format: 'json',
q: `select item from weather.forecast where woeid in
(select woeid from geo.places(1) where text="${location}")`,
},
})
return results.data.query.results.channel.item
}const ora = require('ora')
const getWeather = require('../utils/weather')
module.exports = async (args) => {
const spinner = ora().start()
try {
const location = args.location || args.l
const weather = await getWeather(location)
spinner.stop()
console.log(`Current conditions in ${location}:`)
console.log(`\t${weather.condition.temp}° ${weather.condition.text}`)
} catch (err) {
spinner.stop()
console.error(err)
}
}Теперь, если вы запустите снаружи сегодня --локация "Бруклин, Нью-Йорк" , вы увидите быстрый прядильник, в то время как он делает запрос, а затем текущие погодные условия.
Поскольку запрос произойдет так быстро, может быть трудно увидеть индикатор загрузки. Если вы хотите вручную замедлить его с целью увидеть его, вы можете добавить эту строку в начало вашей погоды функции Util: Жду нового обещания (Resolve => Settimeate (разрешение, 500 0)).
Большой! Теперь давайте скопируем этот код нашему Прогноз ... Команда и измените форматирование немного.
const ora = require('ora')
const getWeather = require('../utils/weather')
module.exports = async (args) => {
const spinner = ora().start()
try {
const location = args.location || args.l
const weather = await getWeather(location)
spinner.stop()
console.log(`Forecast for ${location}:`)
weather.forecast.forEach(item =>
console.log(`\t${item.date} - Low: ${item.low}° | High: ${item.high}° | ${item.text}`))
} catch (err) {
spinner.stop()
console.error(err)
}
}Теперь вы можете увидеть прогноз погоды в 10 дней, когда вы запустите Внешний прогноз - товароодание "Бруклин, Нью-Йорк" Отказ Выглядит неплохо! Давайте добавим еще одну утилиту, чтобы автоматически получить наше местоположение от нашего IP-адреса, если в команде не указано местоположение.
const axios = require('axios')
module.exports = async () => {
const results = await axios({
method: 'get',
url: 'https://api.ipdata.co',
})
const { city, region } = results.data
return `${city}, ${region}`
}// ...
const getLocation = require('../utils/location')
module.exports = async (args) => {
// ...
const location = args.location || args.l || await getLocation()
const weather = await getWeather(location)
// ...
}Теперь, если вы просто запустите внешний прогноз Без места вы увидите прогноз вашего текущего местоположения.
Обработка ошибок
Я не входил в много подробностей о том, как наилучшим образом обращаться с ошибками (это придет в более позднее учебное пособие), но самое важное, что нужно помнить, это использовать правильные коды выхода.
Если ваш CLI когда-либо имеет критическую ошибку, вы должны выйти с Process.exit (1) . Это позволяет терминалу знать, что программа не выходила чисто, которая, например, уведомит вас от службы CI.
Давайте создадим быструю утилиту, которая делает это для нас, поэтому мы можем получить правильный код выхода, когда запущена несуществующая команда.
module.exports = (message, exit) => {
console.error(message)
exit && process.exit(1)
}// ...
const error = require('./utils/error')
module.exports = () => {
// ...
default:
error(`"${cmd}" is not a valid command!`, true)
break
// ...
}Заканчивать
Последний шаг, чтобы получить нашу библиотеку в дикую природу – это публиковать его в менеджере посылки. Поскольку наше приложение написано в JavaScript, имеет смысл публиковать в НПМ. Давайте заполним наш package.json немного больше:
{
"name": "outside-cli",
"version": "1.0.0",
"description": "A CLI app that gives you the weather forecast",
"license": "MIT",
"homepage": "https://github.com/timberio/outside-cli#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/timberio/outside-cli.git"
},
"engines": {
"node": ">=8"
},
"keywords": [
"weather",
"forecast",
"rain"
],
"preferGlobal": true,
"bin": {
"outside": "bin/outside"
},
"scripts": {},
"devDependencies": {},
"dependencies": {
"axios": "^0.18.0",
"minimist": "^1.2.0",
"ora": "^2.0.0"
}
}- Настройка
двигательУбедитесь, что любой установка нашего приложения имеет обновленную версию узла. Поскольку мы используем синтаксис Async/ждут без транспиляции, мы требуем узла 8,0 или выше. - Настройка
Предпочтительнопредупредит пользователь, если установка сNPM установить --saveа неNPM установить --глобалОтказ
Вот и все! Теперь вы можете запустить NPM публиковать И ваше приложение будет доступно для скачивания. Если вы хотите сделать этот шаг вперед и выпустите другие менеджеры по пакетам (например, «Домберит), вы можете проверить PKG или Nexe , что поможет вам объединить свое приложение в автономный двоичный.
Резюме
Это структура, которую мы следуем для всех наших приложений CLI здесь в Древесина И это помогает держать вещи организованными и модульными.
Некоторые Ключевые вынос Из этого учебника для тех, кто только снимал это:
- Файлы Bin – это точка входа для любого приложения CLI и должна вызывать только основную функцию
- Файлы команд не должны требоваться, пока они не будут необходимы
- Всегда включать
помощьиВерсиякоманды - Держите файлы команды Slim – их основная цель – вызовать функции и показать пользовательские сообщения
- Всегда показывать какой-то показатель деятельности
- Выход с правильными кодами ошибок
Я надеюсь, что у вас есть лучшее понимание того, как создавать и организовать приложения CLI в узле. Это первая часть серии учебных пособий, поэтому возвращайся позже, поскольку мы идем более подробно при добавлении дизайна, ASCII Art и Color, принимая пользовательский ввод, написание тестов интеграции и многое другое. Вы можете увидеть весь исходный код, который мы написали сегодня на Github Отказ
Мы здесь в облачную регистрацию здесь @ Пиломатериал . Мы бы понравились, если вы опробуете наш продукт (это серьезно здорово! – Вы можете создать бесплатный аккаунт здесь ), но это все, что мы собираемся рекламировать наш продукт … Вы, ребята, пришли сюда, чтобы узнать о создании приложения CLI в узле и, надеюсь, это руководство помогло вам начать.
Первоначально опубликовано timber.io .
Оригинал: “https://www.freecodecamp.org/news/how-to-create-a-real-world-node-cli-app-with-node-391b727bbed3/”