Как создать приложение командной строки (CLI) с помощью Node.js

Node.js задняя часть Командная строка Программа перевода самородков

atZ3n9vMFjjXDl_XxDtL_FCRSOt6EF0d8LnbMRCCJQUesMme8lzdGpCyMr4-wt1nlIGuoT29EI_tkVpuD_P2mxzbfhbn-ZPcqmZ5QCY_nM9d4ywWEYQxKYc9mjxUnp_uFJzMOMnr

Встроенное в Node.js приложение командной строки (CLI) позволяет автоматизировать повторяющиеся задачи, используя его обширную экосистему. И, благодаря чему-то вродеnpmиyarnТакие инструменты управления пакетами позволяют легко распространять эти приложения командной строки и использовать их на нескольких платформах. В этой статье я опишу, почему вам нужно написать CLI, как это сделать с помощью Node.js, некоторые полезные пакеты и как вы можете опубликовать только что написанный CLI.

Зачем использовать Node.js для создания приложений командной строки

Одной из причин популярности Node.js является богатая экосистема пакетов, которая сейчасnpmрегистрУже более 900 000 пакетов. Написав свой собственный CLI на Node.js, вы сможете подключиться к этой экосистеме, которая также включает в себя огромное количество пакетов, специфичных для CLI. включают:

  • inquirer,enquirerилиprompts, который можно использовать для обработки сложных запросов ввода.
  • email-promptВвод электронной почты можно легко запросить
  • chalkилиkleurДоступен для цветной печати
  • oraхорошая подсказка о загрузке
  • boxenМожет использоваться для добавления границ к вашему выводу
  • stmuxможет предоставить иtmuxАналогичный многотерминальный интерфейс
  • listrМожет отображать список процессов
  • inkCLI можно построить с помощью React
  • meowилиargМожет использоваться для разбора основных параметров
  • commanderиyargsМожет использоваться для более сложного анализа параметров и поддерживает подкоманды.
  • oclifэто фреймворк для создания расширяемых интерфейсов командной строки от Heroku (gluegunкак альтернатива)

Также существует множество удобных способов использования CLI, все они опубликованы вnpm, можно использовать одновременноyarnиnpmсправляться. Напримерcreate-flex-plugin, который можно использовать дляTwilio FlexCLI для создания плагинов. Вы можете установить его с помощью глобальной команды:

# 使用 npm 安装:
npm install -g create-flex-plugin
# 使用 yarn 安装:
yarn global add create-flex-plugin
# 安装之后你就可以使用了:
create-flex-plugin

Или его также можно использовать как зависимость проекта:

# 使用 npm 安装:
npm install create-flex-plugin --save-dev
# 使用 yarn 安装:
yarn add create-flex-plugin --dev
# 安装之后命令将被保存在
./node_modules/.bin/create-flex-plugin
# 或者通过由 npm 支持的 npx 使用:
npx create-flex-plugin
# 以及通过 yarn 使用:
yarn create-flex-plugin

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

отnpmПосле версии 6.1,npm initиyarnОба поддерживают использование CLI для сборки проекта, имя команды похоже наcreate-*. Например, только что сказалcreate-flex-plugin, все, что нам нужно сделать, это:

# 使用 Node.js:
npm init flex-plugin
# 使用 Yarn:
yarn create flex-plugin

Создайте свой первый интерфейс командной строки

Если вы предпочитаете смотреть обучающие видео,Нажмите здесь, чтобы посмотреть обучающее видео на YouTube.

Теперь, когда мы объяснили причины создания CLI с Node.js, давайте начнем создавать CLI. В этом уроке мы будем использоватьnpm, но если вы хотите использоватьyarn, подавляющее большинство команд также одинаковы. Убедитесь, что вы установили его в своей системеNode.jsиnpm.

В этом руководстве мы создадим CLI, выполнив командуnpm init @your-username/project, который может построить новый проект в соответствии с вашими предпочтениями.

Запустите новый проект Node.js, выполнив следующий код:

mkdir create-project && cd create-project
npm init --yes

Затем в корневом каталоге проекта создайте файл с именемsrc/каталог, затем поместите файл с именемcli.jsфайл в этом каталоге и напишите код в файле:

export function cli(args) {
 console.log(args);
}

В этой функции мы будем анализировать логику параметров и запускать фактическую требуемую бизнес-логику. Далее нам нужно создать запись для CLI. Создайте каталог в корневом каталоге проектаbin/Затем создайте файл с именемcreate-projectдокумент. написать код:

#!/usr/bin/env node

require = require('esm')(module /*, options*/);
require('../src/cli').cli(process.argv);

В этом небольшом фрагменте кода делается несколько вещей. Во-первых, мы вводимesmмодуль, который позволяет нам использовать его в других файлахimport. Это не имеет прямого отношения к построению CLI, но для этого руководства нам нужно использоватьмодуль ЕС, в то время как пакетesmПозволяет нам использовать ES-модули без транскодирования, когда версия Node.js его не поддерживает. Затем мы вводимcli.jsфайл и вызвать функциюcli, и воляprocess.argvPass in — массив аргументов, передаваемых в скрипт функции из командной строки.

Прежде чем мы сможем протестировать скрипт, его необходимо установить, выполнив следующую командуesmполагаться:

npm install esm

Кроме того, нам необходимо синхронизировать требование предоставления CLI-скрипта диспетчеру пакетов. метод находится вpackage.jsonДобавьте соответствующую запись в файл. Не забудьте также обновить свойстваdescription,name,keywordиmain:

{
 "name": "@your_npm_username/create-project",
 "version": "1.0.0",
 "description": "A CLI to bootstrap my new projects",
 "main": "src/index.js",
 "bin": {
   "@your_npm_username/create-project": "bin/create-project",
   "create-project": "bin/create-project"
 },
 "publishConfig": {
   "access": "public"
 },
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "keywords": [
   "cli",
   "create-project"
 ],
 "author": "YOUR_AUTHOR",
 "license": "MIT",
 "dependencies": {
   "esm": "^3.2.18"
 }
}

Если вы заметитеbinсвойство, вы заметите, что мы определяем его как объект с двумя парами ключ-значение. В этом объекте определены команды CLI, которые установит менеджер пакетов. В приведенном выше примере мы зарегистрировали две команды для одного и того же скрипта. Один использует свое, добавив наше имя пользователяnpmсфера применения, другая является универсальной для простоты использованияcreate-projectЗаказ.

После этого мы можем протестировать скрипт. Самый простой способ проверить это использоватьnpm linkЗаказ. В терминале вашего проекта запустите:

npm link

Эта команда установит ссылку на ваш текущий проект глобально, поэтому вам не нужно повторно запускать ее при обновлении кода.npm linkЗаказ. Бегnpm linkваша команда CLI уже должна быть доступна. Попробуйте запустить:

create-project

Вы должны увидеть вывод, похожий на:

[ '/usr/local/Cellar/node/11.6.0/bin/node',
  '/Users/dkundel/dev/create-project/bin/create-project' ]

Обратите внимание, что эти два адреса зависят от адреса вашего проекта и адреса установки Node.js и соответственно будут различаться. И этот массив становится длиннее по мере добавления параметров. Попробуйте запустить:

create-project --yes

Вывод теперь отражает добавление нового параметра:

[ '/usr/local/Cellar/node/11.6.0/bin/node',
  '/Users/dkundel/dev/create-project/bin/create-project',
  '--yes' ]

Разбор параметров и обработка ввода

Теперь мы готовы разобрать переданные в скрипт аргументы и придать им логическое значение. Наш CLI поддерживает один параметр и несколько опций:

  • [template]: Мы поддерживаем мульти-шаблон из коробки. Если пользователь не проходит в этом параметре, мы дадим запрос для пользователя выбрать
  • --git: будет работатьgit init, чтобы создать новый проект git
  • --install: он автоматически установит все зависимости для проекта
  • --yes: он пропустит все подсказки и просто использует параметры по умолчанию

Для нашего проекта мы будем использоватьinquirerзапросить параметры и использоватьargбиблиотека для анализа параметров CLI. Установите зависимости, выполнив следующую команду:

npm install inquirer arg

Для начала напишем логику парсинга параметров, процесс парсинга будет парсить параметры вoptionsобъект, для нашего использования. Добавьте следующий код вcli.jsсередина:

import arg from 'arg';

function parseArgumentsIntoOptions(rawArgs) {
 const args = arg(
   {
     '--git': Boolean,
     '--yes': Boolean,
     '--install': Boolean,
     '-g': '--git',
     '-y': '--yes',
     '-i': '--install',
   },
   {
     argv: rawArgs.slice(2),
   }
 );
 return {
   skipPrompts: args['--yes'] || false,
   git: args['--git'] || false,
   template: args._[0],
   runInstall: args['--install'] || false,
 };
}

export function cli(args) {
 let options = parseArgumentsIntoOptions(args);
 console.log(options);
}

бегатьcreate-project --yes, вы сможете увидетьskipPromptстанетtrueили попробуйте передать другие параметры, например.create-project cli,Такtemplateсвойства будут установлены.

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

import arg from 'arg';
import inquirer from 'inquirer';

function parseArgumentsIntoOptions(rawArgs) {
// ...
}

async function promptForMissingOptions(options) {
 const defaultTemplate = 'JavaScript';
 if (options.skipPrompts) {
   return {
     ...options,
     template: options.template || defaultTemplate,
   };
 }

 const questions = [];
 if (!options.template) {
   questions.push({
     type: 'list',
     name: 'template',
     message: 'Please choose which project template to use',
     choices: ['JavaScript', 'TypeScript'],
     default: defaultTemplate,
   });
 }

 if (!options.git) {
   questions.push({
     type: 'confirm',
     name: 'git',
     message: 'Initialize a git repository?',
     default: false,
   });
 }

 const answers = await inquirer.prompt(questions);
 return {
   ...options,
   template: options.template || answers.template,
   git: options.git || answers.git,
 };
}

export async function cli(args) {
 let options = parseArgumentsIntoOptions(args);
 options = await promptForMissingOptions(options);
 console.log(options);
}

сохраните файл и запуститеcreate-project, вы увидите приглашение выбора шаблона, подобное этому:

После этого вас спросят, хотите ли вы инициализироватьgit. После выбора обоих вопросов вы увидите вывод, подобный этому:

{ skipPrompts: false,
  git: false,
  template: 'JavaScript',
  runInstall: false }

попробуй убежатьcreate-project -yкоманда, все подсказки будут игнорироваться на этом этапе. Вы сразу увидите параметры, введенные в командной строке:

написать логику кода

Теперь, когда мы можем определить соответствующие логические параметры с помощью информации о приглашении и параметров командной строки, давайте напишем логический код, который может создать проект. Наш CLI будет работать сnpm initКоманда аналогична записи в существующий каталог и будетtemplatesФайлы в каталоге копируются в проект. Мы также разрешаем параметры для изменения адреса целевого каталога, поэтому вы можете повторно использовать эту логику в других проектах.

Прежде чем мы напишем логический код, создадим файл с именемtemplatesкаталог и поместите каталогtypescriptиjavascriptразмещены в этом каталоге. Все их имена — строчные версии, и мы предложим пользователю выбрать одно из них. В этой статье мы будем использовать эти два имени, но вы можете использовать любое другое имя. В этот каталог поместите файлpackage.jsonИ добавьте любые основные зависимости проекта, которые вам нужны, и любые файлы, которые вам нужно скопировать в проект. Затем наш код скопирует все эти файлы в новый проект. Если вам нужно творческое вдохновение, вы можете просмотреть файлы, которые я использовал, на github.com/dkundel/create-project.

Чтобы скопировать все файлы рекурсивно, мы будем использовать файл под названиемncpбиблиотека. Эта библиотека поддерживает рекурсивное копирование между платформами и даже имеет флаги для принудительной перезаписи существующих файлов. Кроме того, чтобы иметь возможность отображать цветной вывод, мы также установимchalk. Запустите следующий код для установки зависимостей:

npm install ncp chalk

Мы поместим основную логику проекта вsrc/в каталогеmain.jsв файле. Создайте новый файл и добавьте следующий код:

import chalk from 'chalk';
import fs from 'fs';
import ncp from 'ncp';
import path from 'path';
import { promisify } from 'util';

const access = promisify(fs.access);
const copy = promisify(ncp);

async function copyTemplateFiles(options) {
 return copy(options.templateDirectory, options.targetDirectory, {
   clobber: false,
 });
}

export async function createProject(options) {
 options = {
   ...options,
   targetDirectory: options.targetDirectory || process.cwd(),
 };

 const currentFileUrl = import.meta.url;
 const templateDir = path.resolve(
   new URL(currentFileUrl).pathname,
   '../../templates',
   options.template.toLowerCase()
 );
 options.templateDirectory = templateDir;

 try {
   await access(templateDir, fs.constants.R_OK);
 } catch (err) {
   console.error('%s Invalid template name', chalk.red.bold('ERROR'));
   process.exit(1);
 }

 console.log('Copy project files');
 await copyTemplateFiles(options);

 console.log('%s Project ready', chalk.green.bold('DONE'));
 return true;
}

Этот код экспортирует файл с именемcreateProjectНовая функция этой функции сначала проверит, доступен ли указанный шаблон.Метод проверки заключается в использованииfs.accessпроверить читабельность файла (fs.constants.R_OK), затем используйтеncpСкопируйте файл в указанный каталог. Кроме того, после успешного копирования нам также необходимо вывести несколько цветных логов, содержимоеDONE Project ready.

После этого обновитеcli.js, добавив пару новых функцийcreateProjectПризыв к:

import arg from 'arg';
import inquirer from 'inquirer';
import { createProject } from './main';

function parseArgumentsIntoOptions(rawArgs) {
// ...
}

async function promptForMissingOptions(options) {
// ...
}

export async function cli(args) {
 let options = parseArgumentsIntoOptions(args);
 options = await promptForMissingOptions(options);
 await createProject(options);
}

Чтобы проверить наш прогресс, где-то в вашей системе, например.~/test-dirСоздайте новый каталог в , и запустите команду внутри этой папки с шаблоном. Например:

create-project typescript --git

Вы должны увидеть уведомление о том, что проект создан и файлы скопированы в этот каталог.

Теперь осталось сделать еще два шага. Нам нужна настраиваемая инициализацияgitи установить зависимости. Для этого нам понадобятся еще три зависимости:

  • execaИспользуется, чтобы позволить нам легко запускать такие вещи, какgitТакая внешняя команда
  • pkg-installИспользуется для запуска команд в зависимости от того, что использует пользователь.yarn installилиnpm install
  • listrДавайте укажем список задач и предоставим пользователю четкое представление о ходе выполнения

Установите зависимости, выполнив следующую команду:

npm install execa pkg-install listr

обновить позжеmain.js, добавьте следующий код:

import chalk from 'chalk';
import fs from 'fs';
import ncp from 'ncp';
import path from 'path';
import { promisify } from 'util';
import execa from 'execa';
import Listr from 'listr';
import { projectInstall } from 'pkg-install';

const access = promisify(fs.access);
const copy = promisify(ncp);

async function copyTemplateFiles(options) {
 return copy(options.templateDirectory, options.targetDirectory, {
   clobber: false,
 });
}

async function initGit(options) {
 const result = await execa('git', ['init'], {
   cwd: options.targetDirectory,
 });
 if (result.failed) {
   return Promise.reject(new Error('Failed to initialize git'));
 }
 return;
}

export async function createProject(options) {
 options = {
   ...options,
   targetDirectory: options.targetDirectory || process.cwd()
 };

 const templateDir = path.resolve(
   new URL(import.meta.url).pathname,
   '../../templates',
   options.template
 );
 options.templateDirectory = templateDir;

 try {
   await access(templateDir, fs.constants.R_OK);
 } catch (err) {
   console.error('%s Invalid template name', chalk.red.bold('ERROR'));
   process.exit(1);
 }

 const tasks = new Listr([
   {
     title: 'Copy project files',
     task: () => copyTemplateFiles(options),
   },
   {
     title: 'Initialize git',
     task: () => initGit(options),
     enabled: () => options.git,
   },
   {
     title: 'Install dependencies',
     task: () =>
       projectInstall({
         cwd: options.targetDirectory,
       }),
     skip: () =>
       !options.runInstall
         ? 'Pass --install to automatically install dependencies'
         : undefined,
   },
 ]);

 await tasks.run();
 console.log('%s Project ready', chalk.green.bold('DONE'));
 return true;
}

Этот код будет передан в--gitили пользователь, выбранный в подсказкеgitбеги, когдаgit init, и будет передано в--installбеги, когдаnpm installилиyarn, в противном случае он пропустит обе задачи и уведомит пользователя сообщением о том, что если он хочет автоматическую установку, перейдите в--install.

Сначала удалите существующую тестовую папку, создайте новую и посмотрите, как она работает. Запустите команду:

create-project typescript --git --install

В вашей папке вы должны увидеть.gitпапка иnode_modulesпапка, представляющаяgitбыл инициализирован, иpackage.jsonЗависимости, указанные в, уже установлены.

Поздравляем, ваш первый интерфейс командной строки готов к работе!

Если вы хотите, чтобы ваш код был доступен как фактический модуль, чтобы другие могли повторно использовать вашу логику в своем коде, вам также необходимо добавитьsrc/добавить файлы подindex.js, этот файл предоставляетmain.jsСодержание:

require = require('esm')(module);
require('../src/cli').cli(process.argv);

Что дальше?

Теперь, когда ваш код CLI готов, вы можете использовать его и двигаться дальше. Если вы просто хотите использовать его сами и не хотите делиться им с другими, вам нужно продолжать его использовать.npm linkВот и все. На самом деле бегnpm init projectПопробуйте это, ваш код также будет запущен.

Если вы хотите поделиться своими шаблонами кода с другими, вы можете отправить код на GitHub для справки или, что еще лучше, использоватьnpm publishподтолкнуть его как пакет кnpmрегистр. Перед публикацией также необходимо убедиться, чтоpackage.jsonдобавить файлfilesатрибуты, чтобы указать, какие файлы должны быть опубликованы:

 },
 "files": [
   "bin/",
   "src/",
   "templates/"
 ]
}

Если вы хотите проверить, что файл будет опубликован, запуститеnpm pack --dry-runЗатем посмотрите на вывод. использовать послеnpm publishопубликовать CLI. ты сможешь@dkundel/create-projectНайдите мой проект или попробуйте запуститьnpm init @dkundel/project.

Есть много других функций, которые вы можете добавить к нему. В моем проекте я также добавил некоторые зависимости для создания для меняLICENSE,CODE_OF_CONDUCT.mdи.gitignore. Ты сможешьНайдите исходный код, реализующий эти функции, на GitHub., или ознакомьтесь с упомянутыми выше репозиториями для получения дополнительной функциональности. Если вы найдете библиотеку, которая, по вашему мнению, должна быть указана в статье, которой я не указал, или хотите показать мне свой интерфейс командной строки, просто отправьте мне сообщение!

Вы можете создать еще больше с помощью JavaScript:

Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.