Автор оригинала: FreeCodeCamp Community Member.
Питер Бенджамин
Благодаря правильным пакетам, запись приложений командной строки в Nodejs – это бриз.
Один из пакетов, в частности, делает его чрезвычайно легко: Командир Отказ
Давайте установим сцену и прогулку, как написать приложение интерфейса командной строки (CLI) в Nodejs с командиром. Наша цель будет написать приложение CLI для списка файлов и каталогов.
Настраивать
Иды Я люблю онлайн удостоверение личности. Они абстрагируют много головных болей, когда речь идет о настройке среды Dev. Я лично использую Cloud9 по следующим причинам:
- Макет интуитивно понятен
- Редактор красивый и простой в использовании
- Ресурсы свободных уровней были недавно были увеличены до 1 ГБ ОЗУ и дискового пространства 5 ГБ, что более чем достаточно для приличного приложения для приличного размера.
- Неограниченное количество рабочих станций
- Это идеальная среда для тестирования или эксперимента с новыми проектами/идеями, не опасаясь нарушения вашей среды
Узел/NPM версия На момент написания этой статьи узел находится в версии 5.3.0 и NPM – версия AD 3.3.12.
Инициализация
Начнем с инициализации нашего проекта, примите все NPM по умолчанию, а установка командир упаковка:
npm initnpm i --save commander
В результате чего:
Примечание:
- Вам придется добавить Bin Вручную, который рассказывает Nodejs, как называется ваше приложение CLI и какова точка записи в ваше приложение.
- Убедитесь, что вы не используете имя команды, которое уже существует в вашей системе.
Index.js.
Теперь, когда мы инициализировали наш проект и указали, что наша точка входа – index.js, давайте создадим index.js:
touch index.js
Теперь для фактической части кодировки:
Как правило, при выполнении файлов NODEJS мы скажем системе использовать соответствующий интерпретатор с помощью префикса Узел перед файлом. Однако мы хотим иметь возможность выполнить наше приложение CLI глобально из любой точки системы, и без необходимости указывать переводчик узла каждый раз.
Поэтому наша первая строка – Shebang выражение:
#!/usr/bin/env node
Это не только говорит нашей системе использовать соответствующий переводчик, но он также говорит о нашей системе использовать соответствующую Версия переводчика.
Отсюда, мы пишем чистый код JavaScript. Так как я буду писать в соответствии с ES6-совместимым кодом, начну с буквального выражения:
'use strict';
Это говорит компилятору использовать более строгий вариант JavaScript [ 1 ] И позволяет нам написать код ES6 на Cloud9.
Давайте начнем с нужды командир упаковка:
const program = require('commander');Теперь, написание приложений CLI с командир это Просто, и документация отличная, но я боролся с несколькими понятиями, которые я буду пытаться прояснить здесь.
Кажется, что 2 дизайна для приложений CLI. Возьми обман и Гит Например.
С обман , вы проходите один или несколько флагов:
- ls -l.
- ОДНАЯ
С Гит Вы проходите под командования, но у вас также есть несколько флагов:
- Git Compity -AM
GE> - Git Remote Добавить начало
RL>
Мы рассмотрим гибкость Командир Дает нам разработать оба типа интерфейсов командной строки.
Commander API
Командир API прямо вперед, и документация отличная.
Есть 3 основных способа, мы можем написать нашу программу:
Метод № 1: Флаг – только приложение командной строки
const program = require('commander');program .version('0.0.1') .option('-o, --option','option description') .option('-m, --more','we can have as many options as we want') .option('-i, --input [optional]','optional user input') .option('-I, --another-input ','required user input') .parse(process.argv); // end with parse to parse through the input Примечание:
- Краткие и длинные варианты находятся в одной строке (см. Смелый текст на изображении выше)
- -o и -М вернется логический Значения, когда пользователи передают их, потому что мы не указывали ?| по желанию или Требуется пользовательский ввод. -i
- и -Я Захватите пользовательский ввод и передайте значения в наше приложение CLI. Любое значение, заключенное в квадратные кронштейны (например, []), считается необязательным. Пользователь может или не может предоставить значение.
- Считается любое значение, заключенное в угловые кронштейны (E.g. <>). Пользователь должен предоставить значение.
Пример выше позволяет нам реализовать подход только к флагам к нашему приложению CLI. Ожидается, что пользователи будут взаимодействовать с нашим приложением, как так:
//Examples:$ cli-app -om -I hello$ cli-app --option -i optionalValue -I requiredValue
Способ № 2: подкоманда и Флаг на основе командной строки приложения
const program = require('commander');program .version('0.0.1') .command('command [optional]') .description('command description') .option('-o, --option','we can still have add'l options') .action(function(req,optional){ console.log('.action() allows us to implement the command'); console.log('User passed %s', req); if (optional) { optional.forEach(function(opt){ console.log("User passed optional arguments: %s", opt); }); } }); program.parse(process.argv); // notice that we have to parse in a new statement.
Примечание:
- Если мы используем .command («команда …»). Описание («Описание …») мы должны использовать .кция () Чтобы пройти функцию и выполнить наш код на основе ввода пользователя. (Я указываю на это, потому что есть альтернативный метод для использования .Command () Что мы рассмотрим дальше.)
- Если мы используем .command («команда …») , мы больше не можем просто прикрепить .ary (process.argv) В конце концов, в предыдущем примере. Мы должны пройти Разбор () в новом заявлении
Ожидается, что пользователи будут взаимодействовать с нашим приложением CLI, как так:
//Example: $ cli-app command requiredValue -o
Метод № 3: То же, что и метод № 2 выше, но позволяет модулировать код
Наконец, нам не нужно раздувать наш файл JavaScript со всеми .command (). Описание (). Действие () логика. Мы можем модулировать наш проект CLI, как так:
// file: ./cli-app/index.jsconst program = require('commander');program.version('0.0.1').command('command [optional]','command description').command('command2','command2 description').command('command3','command3 description').parse(process.argv); Примечание:
- Если мы используем .Command («команда», «Описание») Передать в команду и описание, мы больше не можем иметь .действие(). Командир будет подразумевать, что у нас есть отдельные файлы с определенной конвенцией именования, где мы можем обрабатывать каждую команду. Конвенция об именах является index-command.js. , index-command2.js , index-command3.js. . Смотрите примеры этого на Github (конкретно: вечера , PM-install , PM-Publish файлы).
- Если мы возьмем этот маршрут, мы можем просто придерживаться .ary () в конце.
Вернуться к нашему сценарию проекта …
Теперь, когда мы охватывали основы, это все вниз отсюда. Мы можем глубоко вздохнуть.
*** ВЗДОХ ***
Хорошо, теперь начинается веселье.
Если мы вспомним наш сценарий проекта, мы хотим написать приложение CLI для списка файлов и каталогов. Итак, начнем написать код.
Мы хотим дать пользователю возможность решить, хочет ли они видеть «все» файлы (включая скрытые) и/или если они хотят видеть формат длинного списка (включая права/разрешения файлов/папок).
Итак, мы начинаем с написания основной оболочки нашей программы, чтобы увидеть наш инкрементный прогресс (мы следим за подписью Метод № 2 для демонстрации):
#!/usr/bin/env node'use strict';
const program = require('commander');program .version('') .command('') .description('') .option('','') .option('','') .action('');program.parse(process.argv);
Давайте начнем заполнять пробелы:
#!/usr/bin/env node'use strict';
const program = require('commander');program .version('0.0.1') .command('list [directory]') .description('List files and folders') .option('-a, --all','List all files and folders') .option('-l, --long','') .action();program.parse(process.argv);
Примечание:
- Мы решили назвать нашу команду список .
- Каталог Аргумент не является обязательным, поэтому пользователь может просто игнорировать, чтобы пройти каталог, в этом случае мы будем перечислены файлы/папки в текущем каталоге.
Итак, в нашем сценарии действительны следующие звонки:
$ cli-app list $ cli-app list -al$ cli-app list --all$ cli-app list --long$ cli-app list /home/user -al
Теперь давайте начнем написать код для нашего .кция () Отказ
#!/usr/bin/env node'use strict';
const program = require('commander');let listFunction = (directory,options) => { //some code here}program .version('0.0.1') ... .action(listFunction);program.parse(process.argv);
Мы собираемся обманывать здесь, используя встроенный Ls Команда, которая доступна во всех Unix-подобных операционным системам.
#!/usr/bin/env node'use strict';
const program = require('commander'), exec = require('child_process').exec;let listFunction = (directory,options) => {const cmd = 'ls';let params = [];if (options.all) params.push('a');if (options.long) params.push('l');let fullCommand = params.length ? cmd + ' -' + params.join('') : cmdif (directory) fullCommand += ' ' + directory;};
program .version('0.0.1') ... .action(listFunction);program.parse(process.argv);
Давайте поговорим об этом коде.
- Во-первых, нам требуется Child_Process Библиотека для выполнения команд Shell * ( * Это открывает большой риск безопасности, который я буду обсуждать позже )
- Мы объявляем постоянную переменную, которая удерживает корню нашей команды
- Мы объявляем массив, который будет проводить какие-либо параметры, переданные пользователем (например, -a , -l )
- Мы проверяем, пройдет ли пользователь короткой рук ( -A ) или длинные флага (- все ). Если так, то Опции. Все и/или Опции. Пожалуйста, будет оценивать правда , в этом случае мы будем толкать соответствующую командный флаг в наш массив. Наша цель – создать команду оболочки, которую мы пройдем позже Child_Process Отказ
- Мы объявляем новую переменную для хранения команды полной оболочки. Если массив параметра содержит какие-либо флаги, мы объединяем флаги друг к другу и в корневую команду. В противном случае, если параметр Array пуст, то мы используем корневую команду как есть.
- Наконец, мы проверяем, пройдет ли пользователь любой необязательными каталог ценности. Если это так, мы объединяем его в полностью построенную команду.
Теперь мы хотим выполнить полностью построенную команду в оболочке. Child_process.exec () дает нам возможность сделать это и Nodejs Docs Дайте нам подпись:
child_process.exec(command, callback(error, stdout, stderr){ //"error" will be returned if exec encountered an error. //"stdout" will be returned if exec is successful and data is returned. //"stderr" will be returned if the shell command encountered an error.})Итак, давайте будем использовать это:
#!/usr/bin/env node'use strict';
const program = require('commander'), exec = require('child_process').exec;let listFunction = (directory,options) => { const cmd = 'ls'; let params = []; if (options.all) params.push('a'); if (options.long) params.push('l'); let fullCommand = params.length ? cmd + ' -' + params.join('') : cmd if (directory) fullCommand += ' ' + directory; let execCallback = (error, stdout, stderr) => { if (error) console.log("exec error: " + error); if (stdout) console.log("Result: " + stdout); if (stderr) console.log("shell error: " + stderr); };exec(fullCommand, execCallback);
};
program .version('0.0.1') ... .action(listFunction);program.parse(process.argv);
Вот и все!
Вот цик моего приложения CLI CLI Отказ
Конечно, мы можем добавить несколько ничути, как:
- Раскраска вывода (я использую Мел Библиотека ниже)
- Современные приложения CLI достаточно умны, чтобы показать текст справки, когда пользователь вызывает программу без параметров или аргументов (много как Git ), поэтому я добавил эту функциональность в самом нижней части.
#!/usr/bin/env node'use strict';/** * Require dependencies * */const program = require('commander'), chalk = require("chalk"), exec = require('child_process').exec, pkg = require('./package.json');/** * list function definition * */let list = (directory,options) => { const cmd = 'ls'; let params = []; if (options.all) params.push("a"); if (options.long) params.push("l"); let parameterizedCommand = params.length ? cmd + ' -' + params.join('') : cmd ; if (directory) parameterizedCommand += ' ' + directory ; let output = (error, stdout, stderr) => { if (error) console.log(chalk.red.bold.underline("exec error:") + error); if (stdout) console.log(chalk.green.bold.underline("Result:") + stdout); if (stderr) console.log(chalk.red("Error: ") + stderr); }; exec(parameterizedCommand,output); };program .version(pkg.version) .command('list [directory]') .option('-a, --all', 'List all') .option('-l, --long','Long list format') .action(list);program.parse(process.argv);// if program was called with no arguments, show help.if (program.args.length === 0) program.help();Наконец, мы можем воспользоваться NPM к символической ссылке нашего приложения CLI, чтобы мы могли использовать его во всем мире в нашей системе. Просто, в терминале CD в корню нашего приложения CLI и типа:
npm link
Окончательные мысли и соображения
Код в этом проекте ни в коем случае не является лучшим кодом. Я полностью осознаю, что всегда есть комната для улучшения, поэтому обратная связь приветствуется!
Кроме того, я хочу указать на недостаток безопасности в нашем приложении. Наш код не Санатифицировать или проверить вход пользователей. Это нарушает лучшие практики безопасности. Рассмотрим следующие сценарии, где пользователи могут пройти от un-желаемого входа:
$ cli-app -al ; rm -rf /$ cli-app -al ; :(){ :|: & };:Если вы хотите написать какой-нибудь код, который обрабатывает эту проблему или исправляет любые другие потенциальные проблемы, обязательно покажите нам свой код, оставив комментарий.
Оригинал: “https://www.freecodecamp.org/news/writing-command-line-applications-in-nodejs-2cf8327eee2/”