- Оригинальный адрес:How to create a real-world Node CLI app with Node
- Оригинальный автор:Timber.io
- Перевод с:Команда облачных переводчиков Alibaba
- Ссылка на перевод:GitHub.com/рассветные команды/…
- Переводчик:Цзин Синь
- Корректор:также дерево,Духовное болото
Создание приложений командной строки с помощью Node
существуетJavaScript
В мире разработки приложениям командной строки еще не уделялось должного внимания. На самом деле, большинство инструментов разработки должны предоставлять интерфейс командной строки для таких разработчиков, как мы, а взаимодействие с пользователем должно быть сравнимо с хорошо созданным веб-приложением, таким как красивый дизайн, простые в использовании меню и понятные интерфейсы. ошибки Обратная связь, подсказки загрузки и индикаторы выполнения и т. д.
Существует не так много практических руководств, которые помогут нам использоватьNode
Создайте интерфейс командной строки, поэтому эта статья будет началом, основанным на базовомhello world
командное приложение, шаг за шагом, чтобы построитьoutside-cli
, который предоставляет текущую погоду и прогнозирует погоду где угодно в ближайшие 10 дней.
намекать: существует довольно много библиотек, которые могут помочь вам создавать сложные приложения командной строки, такие какoclif,yargsа такжеcommander, но для того, чтобы вы лучше понимали его обоснование, мы будем оставлять внешние зависимости как можно меньше. Конечно, мы предполагаем, что у вас уже естьJavaScript
а такжеNode
базовые знания.
начиная
с прочимиJavaScript
Как и в случае с проектами, передовой практикой является созданиеpackage.json
и пустой входной файл, который еще не требует никаких зависимостей, будьте проще.
package.json
{
"name": "outside-cli",
"version": "1.0.0",
"license": "MIT",
"scripts": {},
"devDependencies": {},
"dependencies": {}
}
index.js
module.exports = () => {
console.log('Welcome to the outside!')
}
мы будем использоватьbin
файл для запуска этой новой программы, и он поместитbin
Файл добавляется в системный каталог, чтобы его можно было вызывать из любого места.
#!/usr/bin/env node
require('../')()
Не видел его раньше#!/usr/bin/env node
? Это называетсяshebang. информирует систему о том, что это неshell
script и указать, что следует использовать другой интерпретатор.
bin
Файл должен быть простым, потому что он предназначен только для вызова основной функции, и весь наш код должен быть размещен за пределами этого файла, чтобы обеспечить модульность и тестируемость, а также обеспечить возможность использования в будущем в другом коде. .
Чтобы иметь возможность запускать напрямуюbin
файл, нам нужно дать правильные права доступа к файлу, если вы находитесь вUNIX
среду, вам просто нужно выполнитьchmod +x bin/outside
,Windows
Пользователи могут полагаться только на себя, рекомендуется использоватьLinux
подсистема.
Далее мы добавимbin
файл вpackage.json
, то когда мы устанавливаем этот пакет глобально (npm install -g outside-cli
),bin
Файл автоматически добавляется в системный каталог.
package.json
{
"name": "outside-cli",
"version": "1.0.0",
"license": "MIT",
"bin": {
"outside": "bin/outside"
},
"scripts": {},
"devDependencies": {},
"dependencies": {}
}
Теперь мы входим./bin/outside
, Вы можете запустить его напрямую, приветственное сообщение будет напечатано, выполнить его в вашем корневом каталоге проектаnpm link
, это создаст мягкую ссылку между системным путем и вашим двоичным файлом, так чтоoutside
Команду можно запустить где угодно.
Приложение CLI состоит из параметров и директив, где параметры (или «флаги») — это значения с префиксом в один или два дефиса (например,-d
,--debug
или--env production
), что очень полезно для приложений. Директивы относятся ко всем остальным значениям без флагов.
В отличие от директив, аргументы не требуют определенного порядка, например, запускoutside today Brooklyn
, надо договориться, что вторая команда может представлять только регион, использовать--
В противном случае запуститеoutside today --location Brooklyn
, вы можете легко добавить дополнительные параметры.
Чтобы сделать приложение более практичным, нам нужно разобрать директиву и параметры, а затем преобразовать в литеральные объекты, мы можем использоватьprocess.argv
сделать это вручную, но сейчас мы хотим установить первую зависимость проектаminimist, пусть это сделает это за нас.
npm install --save minimist
index.js
const minimist = require('minimist')
module.exports = () => {
const args = minimist(process.argv.slice(2))
console.log(args)
}
намекать:потому чтоprocess.argv
Первые два аргумента — это интерпретатор и имя двоичного файла, поэтому мы используем.slice(2)
Удалите первые два параметра и заботьтесь только о других переданных командах.
выполнить сейчасoutside today
будет выводить{ _: ['today'] }
. воплощать в жизньoutside today --location "Brooklyn, NY"
, выведет{ _: ['today'], location: 'Brooklyn, NY' }
. Но теперь нам не нужно углубляться в использование параметров, подождите до фактического использованияlocation
Мы продолжим копать глубже в то время, и мы знаем пока достаточно, чтобы реализовать первую инструкцию.
синтаксис параметра
в состоянии пройтиэта статьяПомогает лучше понять синтаксис параметра. По сути, параметр может иметь один или два дефиса, за которыми следует соответствующее значение, которое по умолчанию равноtrue
, параметры с одним дефисом также могут использовать сокращенную форму (-a -b -c
или-abc
соответствовать{ a: true, b: true, c: true }
).
Если значение параметра содержит специальные символы или пробелы, оно должно быть заключено в кавычки.. Например--foo bar
соответствующий{ : ['baz'], foo: 'bar' }
,--foo "bar baz"
вести переписку{ foo: 'bar baz' }
.
Рекомендуется разделить код для каждой инструкции и загрузить его в память при вызове, что помогает сократить время запуска и избежать ненужных нагрузок. В основном коде команды просто используйтеswitch
Эта практика может быть достигнута. В этой настройке нам нужно записать каждую директиву в отдельный файл и экспортировать функцию, и в то же время мы передаем аргументы каждой функции директивы для последующего использования.
index.js
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
}
}
cmds/today.js
module.exports = (args) => {
console.log('today is sunny')
}
Теперь, если вы выполнитеoutside today
, вы увидите выводtoday is sunny
, если выполняетсяoutside foobar
, выведет"foobar" is not a valid command
. Текущий прототип очень хорош, а дальше нам нужно получить реальные данные о погоде через API.
Есть некоторые команды и параметры, которые мы хотели бы включить в каждое приложение командной строки:help
,--help
а также-h
Используется для отображения списка помощи;--version
а также-v
Используется для отображения информации о версии текущего приложения. Когда директива не указана, мы также должны отображать список помощи по умолчанию.
Minimist
автоматически проанализирует параметры как пары ключ-значение, поэтому запуститеoutside --version
сделаюargs.version
равныйtrue
. Затем в программе установивcmd
переменная для сохраненияhelp
а такжеversion
Результат оценки параметра, а затем вswitch
Вышеупомянутая функция может быть достигнута путем добавления двух операторов обработки к оператору.
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
}
}
При реализации новых директив формат должен быть таким же, какtoday
Инструкции остаются прежними.
cmds/version.js
const { version } = require('../package.json')
module.exports = (args) => {
console.log(`v${version}`)
}
cmds/help.js
const menus = {
main: `
outside [command] <options>
today .............. show weather for today
version ............ show package version
help ............... show help menu for a command`,
today: `
outside today <options>
--location, -l ..... the location to use`,
}
module.exports = (args) => {
const subCmd = args._[0] === 'help'
? args._[1]
: args._[0]
console.log(menus[subCmd] || menus.main)
}
Теперь, если вы выполнитеoutside help today
илиoutside toady -h
, ты увидишьtoday
справочная информация по команде, выполнитьoutside
илиoutside -h
То же самое справедливо.
Текущая настройка проекта приятна, потому что, когда вам нужно добавить новую директиву, вы просто создаете новый файл директивы и добавляете его вswitch
оператор, а затем установите для него справочное сообщение.
cmds/forecast.js
module.exports = (args) => {
console.log('tomorrow is rainy')
}
index.js
*// ...*
case 'forecast':
require('./cmds/forecast')(args)
break
*// ...*
cmds/help.js
const menus = {
main: `
outside [command] <options>
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 <options>
--location, -l ..... the location to use`,
forecast: `
outside forecast <options>
--location, -l ..... the location to use`,
}
// ...
Некоторые инструкции могут выполняться долго. Если бы вы выполнили изAPI
Извлечение данных, генерация контента, запись файлов на диск или любая другая программа, которая занимает больше нескольких миллисекунд, тогда вам необходимо предоставить пользователю обратную связь о том, что ваша программа все еще отвечает. Вы можете использовать индикатор выполнения, чтобы показать ход выполнения операции, или вы можете напрямую отобразить индикатор выполнения.
Для текущего приложения у нас нет возможности узнатьAPI
прогресс запроса, поэтому мы используем простойspinner
чтобы показать, что программа все еще работает. Затем мы устанавливаем две зависимости,axios
для сетевых запросов,ora
реализоватьspinner
.
npm install --save axios ora
Получить данные из API
Теперь давайте создадим служебную функцию, которая использует Yahoo Weather API для получения данных о погоде в определенном регионе.
намекать: Yahoo API использует очень лаконичныйYQL
Грамматика, нам не нужно специально ее понимать, просто скопируйте и используйте ее напрямую. Кроме того, это единственное, что я нашел, которому не нужно предоставлятьAPI key
API погоды.
utils/weather.js
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
}
cmds/today.js
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)
}
}
теперь, когда вы выполняетеoutside today --location "Brooklyn, NY"
, сначала вы увидите быстро вращающийсяspinner
Появится во время запроса из приложения, а затем отображается информация о погоде.
Когда запрос произойдет быстро, нам трудно увидеть индикатор загрузки. Если вы хотите искусственно замедлить скорость, вы можете добавить это предложение до функции инструмента погоды запроса:await new Promise(resolve => setTimeout(resolve, 5000))
.
Превосходно! Затем мы копируем приведенный выше код для достиженияforecast
команду, а затем просто измените выходной формат.
cmds/forecast.js
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)
}
}
теперь, когда вы выполняетеoutside forecast --location "Brooklyn, NY"
После этого вы увидите результаты прогноза погоды на следующие 10 дней. Далее мы добавим вишенку на торт, когдаlocation
Время не указано, служебная функция, которую мы пишем для автоматического получения местоположения на основе IP-адреса.
utils/location.js
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}`
}
cmds/today.js & cmds/forecast.js
*// ...*
const getLocation = require('../utils/location')
module.exports = async (args) => {
*// ...*
const location = args.location || args.l || await getLocation()
const weather = await getWeather(location)
*// ...*
}
Теперь вы не добавляетеlocation
После выполнения команды с параметрами вы увидите информацию о погоде, соответствующую текущему региону.
обработка ошибок
Мы не будем вдаваться в подробности наилучшего способа обработки ошибок в этой статье (в одном из последующих руководств), но самое главное — не забывать использовать правильный код выхода.
Если в вашем приложении командной строки произошла фатальная ошибка, вы должны использоватьprocess.exit(1)
, терминал воспримет, что программа выполнена не полностью, и сможет уведомить внешний мир через программу CI.
Затем мы создаем служебную функцию, которая заставит программу выдавать правильный код выхода при выполнении несуществующей инструкции.
utils/error.js
module.exports = (message, exit) => {
console.error(message)
exit && process.exit(1)
}
index.js
*// ...*
const error = require('./utils/error')
module.exports = () => {
*// ...*
default:
error(`"${cmd}" is not a valid command!`, true)
break
*// ...*
}
окончание
Последний шаг — опубликовать написанную нами библиотеку на удаленной платформе управления пакетами, поскольку мы используемJavaScript
,NPM
Не может быть более подходящим. Теперь нам нужно заполнить дополнительную информацию, чтобы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"
}
}
-
настраивать
engine
гарантирует, что у пользователя есть более новаяNode
Версия. Потому что мы использовали его напрямуюasync/await
, поэтому мы требуемNode
Версия должна быть 8.0 и выше. -
настраивать
preferGlobal
Во время установки пользователю будет предложено указать, что библиотеку лучше всего устанавливать глобально, а не как локальную зависимость.
На этом пока все, теперь можно проходитьnpm publish
Опубликовано на удаленном компьютере для загрузки другими пользователями. Если вы хотите пойти дальше, опубликуйте в других инструментах управления пакетами (например,Homebrew
), ты можешь понятьpkgилиnexe, они помогут вам упаковать ваше приложение в автономный двоичный файл.
Суммировать
Структура каталогов кода, описанная в этой статье,TimberДалее следуют все приложения командной строки, указанные выше, и это помогает поддерживать организованность и модульность.
Для скорочтения мы также даем несколько советов для этого урока.ключевые выводы:
-
Bin
Файл является точкой входа всего приложения командной строки, и его ответственность заключается только в вызове основной функции. -
Файл директивы не должен загружаться в основную функцию, когда она не выполняется.
-
всегда включать
help
а такжеversion
инструкция. -
Файлы инструкций должны быть простыми, их основная обязанность — вызывать другие служебные функции, а затем отображать информацию для пользователя.
-
Всегда включайте некоторые инструкции по запуску для пользователя.
-
Приложение должно выйти с правильным кодом выхода.
Надеюсь, теперь вы лучше понимаете, как использоватьNode
Создавайте и организуйте приложения командной строки. Эта статья только начало, дальше будем разбираться как оптимизировать дизайн, генерироватьascii art
добавление цвета и т.д. Исходный код этой статьи можно найти по адресуGitHubполучено выше.