Как создать инструмент CLI в NodeJS
Как разработчики, мы как бы живем с инструментами CLI. Начиная от git
и до cloud shells
- мы используем эти инструменты везде. Итак, пришло время сделать свой собственный. В этом процессе мы будем использовать великолепную среду oclif от Heroku.
Что такое Oclif?
Это открытая среда для быстрого создания инструментов CLI, предоставляемая хорошо известным Heroku .
Что будет в результате?
Мы сделаем команду todo list, которая может иметь четыре действия:
- Добавить новое задание
- Посмотреть все задачи
- Обновить задачу
- Удалить задачу
Инициализировать наш проект
Oclif может генерировать два типа проектов -
- Проекты, которые имеют одну команду.
- Проекты, которые могут иметь несколько команд, включая вложенные.
В этой статье нам понадобится проект с несколькими командами, поэтому давайте сгенерируем его:
npx oclif multi todocli
Выполнение этой команды и следование инструкциям инициализирует новый проект с именем todocli
в текущем каталоге.
Теперь давайте перейдем в каталог и запустим help:
cd todocli # А затем ./bin/run --help
Получим следующий результат:
> USAGE $ todocli [COMMAND] COMMANDS hello help display help for todocli
Это показывает доступные команды и их документацию.
Структура проекта
Внутри src
каталога мы можем найти каталог с именем commands
. Этот каталог содержит все команды с их относительными именами файлов. Например, если у нас есть команда, hello
в каталоге будет файл с именем hello.js
, и команда будет работать без какой-либо дополнительной настройки. Давайте удалим, hello.js
поскольку нам это не понадобится.
Настройка базы данных
Для хранения наших задач нам нужна система хранения. Для простоты мы будем использовать lowdb - довольно простую систему хранения файлов в виде json.
Давайте установим ее:
npm install lowdb --save
После установки lowdb создадим файл db.js
со следующим содержимым:
// db.js const low = require('lowdb'); const FileSync = require('lowdb/adapters/FileSync'); const adapter = new FileSync('db.json'); const db = low(adapter); // Установим занчения по умолчанию и запишим их в нашу базу db.defaults({todos: []}).write(); const Todo = db.get('todos'); module.exports = { db, Todo }
Здесь мы просто загружаем необходимую библиотеку и файл db.json (пустой файл), а затем определяем пустой массив todos в качестве нашей базовой коллекции (это похоже на таблицу, если вы знакомы с SQL базами данных).
Добавление задач
oclif предоставляет нам простую функциональность для генерации команд. Давайте запустим следующее:
oclif command add
Она создаст файл с именем add.js
внутри src/commands
каталога. Давайте заменим содержимое этого файла на код ниже:
// add.js const { Command, flags } = require('@oclif/command'); const { Todo } = require('../db'); class AddCommand extends Command { async run() { const { flags: { task } } = this.parse(AddCommand); const res = await = Todo.push({ task, id: Todo.value().length, done: false, }).write(); this.log(res); } } AddCommand.description = `Add a new Todo ... Adds a new todo to the existing list ` AddCommand.flags = { task: flags.string({ char: 'n', description: 'task' }), }; module.exports = AddCommand;
Файл имеет несколько ключевых компонентов:
- функция run, которая выполняет основные функции этой команды,
- descriptions, это документация для команды,
- flags, которые описывают флаги, передаваемые команде.
Здесь у нас есть флаг с именем, task
который имеет тип string
. Мы можем запустить команду следующим образом:
./bin/run add --task="welcome task"
Эта команда добавит задачу в нашу базу данных и напечатает ответ этой операции.
Показать список задач
// show.js const { Command } = require('@oclif/command'); const chalk = require('chalk'); const { Todo } = require('../db'); class ShowCommand extends Command { async run() { const res = await Todo.sortBy('id').value(); res.forEach(({id, task, done}) => { this.log( `${chalk.magenta(id)} ${ done ? chalk.green('DONE') : chalk.yellowBright('NOT DONE') } : ${task}` ); }); } } ShowCommand.description = `Shows existing task ... Show all the task sorted by their ids ` module.exports = ShowCommand;
Здесь внутри show.js
мы показываем все задачи в порядке возрастания. Мы добавили немного цвета, с помощью chalkjs
чтобы результаты нашей команды выглядели лучше.
Обновление задач
// update.js const { Command, flags } = require('@oclif/command'); const { Todo } = require('../db'); class UpdateCommand extends Command { async run() { const { flags: { id }, } = this.parse(UpdateCommand); const res = await Todo.find({ id: parseInt(id, 10) }).assign({ done: true }).write(); this.log(res) } } UpdateCommand.description = `Marks a task as done ... Marks a task as done ` UpdateCommand.flags = { id: flags.string({ char: 'n', description: 'task id' }), }; module.exports = UpdateCommand;
Для простоты мы сейчас просто маркируем задачи, как done
при обновлении. Мы просто передали id
задачи как flag
.
./bin/run update --id=1
Эта команда установит done = true
для задачи с id = 1
.
Удаление задач
// remove.js const { Command, flags } = require('@oclif/command'); const { Todo } = require('../db'); class RemoveCommand extends Command { async run() { const { flags: { id }, } = this.parse(RemoveCommand); const res = await Todo.remove({ id: parseInt(id, 10) }).write(); this.log(res) } } RemoveCommand.description = `Removes a task by id ... Removes a task permanently from database by id ` RemoveCommand.flags = { id: flags.string({ char: 'n', description: 'task id' }), }; module.exports = RemoveCommand;
Удаление довольно просто: мы передаем id
как флаг, а затем удаляем соответствующую задачу из нашей базы данных.
В заключение
Для того чтобы нашим todo лист могли пользоваться не только вы но и ваши друзья, его можно опубликовать в npm, как это сделать, мы рассматривали в предыдущей статье: Как публиковать пакеты в npm