O que é uma ferramenta CLI?

As ferramentas CLI permitem que você execute certas tarefas ou operações diretamente do seu terminal ou prompt de linha de comando. Eles podem ser construídos usando diferentes linguagens de programação, e uma maneira de criar uma ferramenta CLI é usando Node.js.

Neste artigo, você aprenderá a criar uma ferramenta CLI usando Node.js, testá-la e publicá-la no npm.

Criaremos uma ferramenta chamada todos-cli que permite ao usuário visualizar sua lista de tarefas, adicionar itens a ela e, em seguida, verificar esses itens.

Você pode encontrar o código completo para este tutorial neste Repositório GitHub .

Configurando o projeto

Primeiro, crie um diretório que manterá a ferramenta CLI:

 mkdir todos-cli
cd todos-cli

A seguir, inicializaremos nosso projeto Node.js:

 npm init

Você será solicitado a inserir algumas informações. Você pode pular isso se quiser, passando o argumento -y para o comando:

 npm init-y

Isso irá definir as informações para os seus padrões. Feito isso, precisamos instalar alguns pacotes que ajudarão a criar nossa ferramenta CLI. São eles:

  1. commander : Este pacote torna a criação da ferramenta CLI mais fácil. Ele fornece funções que nos permitem definir comandos, opções e muito mais
  2. giz : Este pacote nos permite imprimir mensagens coloridas no console. Isso nos ajudará a deixar nossa ferramenta CLI bonita e bonita
  3. conf : Este pacote nos permite salvar informações persistentes sobre o usuário máquina. Iremos usá-lo para salvar a lista de tarefas do usuário

Para instalar esses pacotes, execute:

 npm i comandante giz conf

Assim que a instalação dos pacotes for concluída, estamos prontos para iniciar o desenvolvimento da ferramenta CLI.

Criando uma ferramenta CLI

Crie um arquivo index.js na raiz do projeto. Esta será a entrada principal da ferramenta CLI que inicializará os comandos que ela terá.

NOTA: Se você estiver usando o Windows para desenvolvimento, certifique-se de que o caractere de final de linha esteja definido como LF em vez de CRLF ou o ferramenta não funcionará. A maioria dos editores de codificação tem a opção de definir isso. Além disso, o teste no Windows pode não funcionar, então sugiro usar outro sistema operacional ou usar Subsistema Windows para Linux (WSL). No entanto, sua ferramenta publicada no final funcionará em todos os sistemas operacionais sem problemas.

Para garantir que sua ferramenta CLI funcione corretamente, adicione o seguinte código ao início de index.js :

 #!/usr/bin/env node

A seguir, para criar uma CLI com configurações e funcionalidades básicas, podemos usar o commander . Primeiro, vamos exigir o programa do commander :

 const {programa}=requer ('comandante')

Para declarar um comando, usaremos as seguintes funções:

  1. program.command : recebe uma string que define o formato do comando
  2. program.description : descreve o comando para o usuário. Isso é útil quando o usuário executa nossa ferramenta com a opção --help
  3. program.option : as opções que este comando pode assumir, se houver
  4. program.action : a ação que este comando executa, que será uma função

Usaremos essas funções para declarar nossos comandos. Mas quais são os nossos comandos para esta ferramenta CLI?

Precisamos dos seguintes comandos:

  1. lista de tarefas : listará as tarefas na lista de tarefas do usuário
  2. todos add : isso adicionará uma nova tarefa à lista de tarefas do usuário
  3. todos mark-done : marcará tarefas específicas ou todas as tarefas como concluídas na lista

Configurando o comando de lista

O comando list mostrará apenas a lista de tarefas que o usuário adicionou antes. Não terá nenhuma opção. O usuário deve ser capaz de executá-lo apenas executando o seguinte comando em seu terminal:

lista

 todos

Em index.js , adicione o seguinte abaixo do código que adicionamos anteriormente:

 programa .command ('lista') .description ('Listar todas as tarefas TODO') .lista de Ação)

Como você pode ver, estamos usando a função command para declarar o comando em nossa ferramenta CLI. O argumento que você passa é uma string que mostra o formato do comando esperado. Também estamos usando a função descrição para descrever este comando para o usuário quando ele executa nossa ferramenta com a opção --help . Por fim, estamos atribuindo a ação a uma função chamada list , que criaremos em breve.

Manter nossos comandos em arquivos separados ajuda a tornar nosso código legível e de fácil manutenção.

Agora, crie o arquivo comandos/list.js . Este arquivo conterá a função que será executada sempre que o usuário executar a lista de tarefas em seu terminal. Esta função irá apenas recuperar a lista de tarefas da configuração e exibi-las.

Para armazenar e recuperar as tarefas, usaremos o pacote conf . Possui as seguintes funções:

  1. set : define as informações de que precisamos em uma chave específica
  2. get : obtém as informações que definimos antes em uma chave específica

Então, vamos começar exigindo e instanciando conf em comandos/list.js :

 const conf=new (require ('conf')) ()

Em seguida, precisamos implementar a função de lista, que iremos exportar para uso em index.js :

 lista de funções () { }

A função list não aceita argumentos, pois o comando list não possui opções ou argumentos.

Dentro da função de lista, iremos apenas recuperar os dados sob a chave todo-list que será um array e exibirá cada uma das tarefas TODO. lista de tarefas será uma matriz de objetos do seguinte formato:

 { text,//string, o texto da tarefa a fazer done,//booleano, se a tarefa de todo está marcada como concluída ou não
}

Agora que sabemos como será a estrutura de nossos dados, vamos voltar para a função list . A primeira coisa que precisamos fazer é recuperar a lista de tarefas da lista de tarefas pendentes:

 const todoList=conf.get ('todo-list')

A seguir, se o usuário tiver tarefas em sua lista de tarefas, faremos um loop sobre elas e exibiremos as que foram feitas em verde e as que ainda não foram feitas em amarelo. Também informaremos ao usuário o que cada cor significa no início, usando uma cor azul.

Se o usuário não tiver tarefas em sua lista de tarefas, mostraremos uma mensagem em vermelho indicando que não há tarefas em sua lista de tarefas.

Como mencionamos antes, usaremos giz para colorir nossas mensagens para o usuário no terminal. Então, vamos exigir isso após conf no início de comandos/list.js :

 const conf=new (require ('conf')) ()
const giz=requer ('giz') //resto do nosso código

Então, dentro da função list , vamos adicionar nossas condições if como mencionamos:

 const todoList=conf.get ('todo-list') if (todoList && todoList.length) { //o usuário tem tarefas em todoList
} senão { //o usuário não tem tarefas em todoList
}

Vamos trabalhar na parte do else primeiro. Precisamos exibir uma mensagem informando ao usuário que ele não tem nenhuma tarefa em sua lista de tarefas pendentes. Precisamos exibi-lo em vermelho usando giz :

 else { //o usuário não tem tarefas em todoList console.log ( giz.red.bold ('Você ainda não tem tarefas.') )
}

Como você pode ver no código, podemos gerar mensagens em cores diferentes com giz usando giz.COLOR e você pode torná-lo negrito usando giz.COLOR.bold . COLOR pode ser vermelho, azul, amarelo, verde, etc.

Parte do caso de uso da função list está concluída. A próxima parte seria mostrar as tarefas quando o usuário tem tarefas. Primeiro, mostraremos ao usuário uma mensagem que detalha o significado das cores das tarefas:

 if (todoList && todoList.length) { console.log ( giz.blue.bold ('As tarefas em verde foram concluídas. As tarefas em amarelo ainda não foram concluídas.') )
}

Aqui, estamos exibindo em negrito azul.

A seguir, faremos um loop em todoList e, para cada tarefa, verificaremos se foi feito e, em caso afirmativo, exibiremos em verde. Caso contrário, exiba em amarelo:

 todoList.forEach ((tarefa, índice)=> { if (task.done) { console.log ( giz.greenBright (`$ {index}. $ {task.text}`) ) } senão { console.log ( giz.yellowBright (`$ {index}. $ {task.text}`) ) } })

Nossa função list está concluída! Finalmente, para poder usá-lo em index.js , vamos exportar a função:

 module.exports=list

O código completo para command/list.js deve ser:

 const conf=new (require ('conf')) ()
const giz=requer ('giz')
lista de funções () { const todoList=conf.get ('todo-list') if (todoList && todoList.length) { console.log ( giz.blue.bold ('As tarefas em verde foram concluídas. As tarefas em amarelo ainda não foram concluídas.') ) todoList.forEach ((tarefa, índice)=> { if (task.done) { console.log ( giz.greenBright (`$ {index}. $ {task.text}`) ) } senão { console.log ( giz.yellowBright (`$ {index}. $ {task.text}`) ) } }) } senão { console.log ( giz.red.bold ('Você ainda não tem tarefas.') ) }
}
module.exports=list

Vamos voltar para index.js . Só precisamos exigir a lista :

 const list=require ('./comandos/lista')

Em seguida, adicione ao final do arquivo o seguinte:

 program.parse ()

Isso é necessário para o comandante . Assim que terminarmos de declarar nossos comandos, analisamos a entrada do usuário para que o comandante possa descobrir qual comando o usuário está executando e executá-lo.

Testando a ferramenta CLI

Nossa CLI está pronta para ser testada agora. A primeira etapa do teste é adicionar a seguinte chave em package.json :

"bin": { "todos":"index.js"
}

todos será usado no terminal ao executar comandos de nosso comando todos-cli . Você pode alterá-lo para o que quiser. Estamos apontando para index.js , pois este é nosso principal ponto de entrada.

Esta etapa não é importante apenas para testar a ferramenta, mas também para publicá-la posteriormente. Portanto, certifique-se de adicioná-lo.

A seguir, vamos executar o seguinte para instalar nosso pacote globalmente em nossa máquina:

 npm i-g

Feito isso, podemos agora executar nossa ferramenta diretamente do terminal! Vamos testar executando:

 todos--help

Você verá informações sobre nossa CLI e poderá ver que lista está em Comandos:

Listar sob comandos

Agora, vamos tentar executar nosso comando list:

lista

 todos

Isso apenas nos mostrará que ainda não temos tarefas. Vamos agora implementar um novo comando que adiciona tarefas.

Adicionar comando

O comando add terá um argumento, que será o texto da tarefa. Aqui está um exemplo de como será a aparência do comando.

 todos add"Make Dinner"

“Fazer o Jantar” é o argumento que será o texto da tarefa. Estamos usando citações porque tem um espaço. Você também pode tentar escapar do espaço usando \ . Se o texto não tiver espaço, as citações não são necessárias.

Para adicionar um novo comando, em index.js sob a declaração do comando list e antes de program.parse () , adicione o seguinte:

 programa .command ('adicionar ') .description ('Adicionar uma nova tarefa TODO') .action (adicionar)

Como você pode ver, passamos para a função command add onde é o argumento que o usuário passa. No commander , quando um argumento é necessário, usamos , enquanto se for opcional, usamos [ARG_NAME] . Além disso, o nome dado ao argumento é o nome do parâmetro passado para a função em ação .

Agora, precisamos implementar a função add . Como fizemos para list , vamos criar o arquivo comandos/add.js com o seguinte conteúdo:

 const conf=new (require ('conf')) ()
const giz=requer ('giz') função add (tarefa) { } module.exports=add

Observe que passamos task para a função add , que será o argumento passado pelo usuário.

A função add pegará a tarefa e a armazenará no array todos-list usando conf . Em seguida, mostraremos ao usuário uma mensagem de sucesso em verde usando giz .

Iremos primeiro recuperar todo-list de conf , então enviar a nova tarefa para a matriz e usar conf.set para definir o novo valor de todo-list .

Aqui está o código completo para a função add :

 função add (tarefa) { //obtém a lista de tarefas atual let todosList=conf.get ('todo-list') if (! todosList) { //valor padrão para todos-list todosList=[] } //envia a nova tarefa para a lista de todos todosList.push ({ texto: tarefa, feito: falso }) //definir todos-list em conf conf.set ('todo-list', todosList) //exibe mensagem para o usuário console.log ( giz.green.bold ('Tarefa adicionada com sucesso!') )
}

É muito simples! Depois de criar o comando list, as coisas estão ficando mais claras e fáceis de entender.

Agora, voltamos para index.js e exigimos a função add que acabamos de criar:

 const add=require ('./comandos/add')

Vamos testar. Em seu terminal, execute:

 todos add"Make Dinner"

Iremos receber a mensagem “Tarefa adicionada com sucesso!” em verde. Para verificar se a tarefa foi realmente adicionada, execute em seu terminal:

lista

 todos

E você pode ver a tarefa que acabou de adicionar. Experimente adicionar algumas tarefas para ver a lista crescer.

O último comando que adicionaremos é o comando mark-done , que marcará uma tarefa como concluída .

comando mark-done

O comando mark-done , por padrão, marcará todas as tarefas como concluídas. No entanto, se passarmos a opção --tasks seguida por pelo menos um índice das tarefas que queremos marcar como concluídas, ele apenas as marcará como concluídas.

Aqui está um exemplo.

 todos mark-done--tarefas 1 2

Para a simplicidade do tutorial, estamos usando apenas os índices de tarefas para marcá-las como concluídas. Em um caso de uso da vida real, você provavelmente atribuiria IDs às tarefas que seriam exclusivas e aleatórias.

Vamos declarar nosso novo comando abaixo do comando add :

 programa .command ('marcação concluída') .description ('Marcar comandos concluídos') .option ('-t,--tasks ','As tarefas a serem marcadas como concluídas. Se não for especificado, todas as tarefas serão marcadas como concluídas.') .action (markDone)

A principal diferença entre este comando e os comandos anteriores é o uso da função opção . O primeiro parâmetro é o formato da opção. -t,--tasks significa que o usuário pode usar -t ou --tasks para passar esta opção. significa que pode ser mais de uma tarefa, mas como estamos usando <> , isso significa que deve incluir pelo menos uma. O segundo parâmetro é a descrição da opção. Isso é útil quando o usuário executa todos mark-done--help .

A seguir, criaremos a função markDone . Assim como fizemos antes, vamos criar o arquivo comandos/markDone.js com o seguinte conteúdo:

 const conf=new (require ('conf')) ()
const giz=requer ('giz') function markDone ({tasks}) { }
module.exports=markDone

Como você pode ver, markDone pega um objeto que inclui uma propriedade tasks . Se o usuário passar a opção -t ou --tasks para o comando, tarefas será uma matriz dos valores que o usuário passa. Caso contrário, será indefinido.

O que precisamos fazer dentro da função markDone é recuperar o array todo-list de conf . Se a lista de tarefas não estiver vazia, faça um loop sobre ela. Se tasks for um array que contém pelo menos um item, marque apenas as tarefas dos índices que o usuário insere como concluídas. Se tarefas for indefinido, marque todas as tarefas como concluídas.

Esta será a função markDone :

 função markDone ({tarefas}) { let todosList=conf.get ('todo-list') if (todosList) { //faz um loop nas tarefas da lista de afazeres todosList=todosList.map ((tarefa, índice)=> { //verifique se o usuário especificou as tarefas a serem marcadas como concluídas if (tarefas) { //verifique se esta tarefa é uma das tarefas que o usuário especificou if (tasks.indexOf (index.toString ())!==-1) { //marque apenas tarefas especificadas pelo usuário como concluídas task.done=true } } senão { //se o usuário não especificou tarefas, marque todas como concluídas task.done=true } tarefa de retorno }); //definir a nova lista de tarefas conf.set ('todo-list', todosList) } //mostra uma mensagem ao usuário console.log ( giz.green.bold ('As tarefas foram marcadas como concluídas com sucesso') )
}

Estamos fazendo um loop em todosList (se não estiver vazio) dentro do map . Em seguida, verificamos se tarefas está definido (o que significa que o usuário passou por tarefas específicas para marcar como concluídas).

Se tarefas for definido, verifique se o item de tarefa atual na iteração é uma das tarefas que o usuário especificou, verificando se o índice está no array tasks . Observe que estamos usando index.toString () porque o array tasks conterá os indicadores que o usuário inserir como strings. Se o índice fizer parte do array tasks , marque-o como concluído, caso contrário, nada muda.

Se, entretanto, tasks não estiver definido, então, como mencionamos antes, marcaremos todos os itens como concluídos. Assim que o loop estiver concluído e tivermos a lista atualizada, estamos definindo todo-list usando conf.set para o novo array. No final, mostramos ao usuário uma mensagem de sucesso.

Finalmente, vamos voltar para index.js e exigir nossa função markDone :

 const markDone=require ('./comandos/markDone')

Agora, podemos testá-lo. Tente primeiro marcar todas as tarefas realizadas executando:

 todos marcados

Se tudo estiver correto, você pode executar lista de todos e ver se todos os itens estão em verde agora.

Em seguida, tente adicionar mais algumas tarefas e, em seguida, marcar aquelas concluídas usando seus índices, um exemplo de marcação de uma única tarefa como concluída:

 todos mark-done-t 1

Ou para marcar várias tarefas:

 todos mark-done-t 1 3 6

Você pode tentar qualquer combinação e verificar quais estão marcadas como concluídas e quais não estão usando o comando todos list .

Nossa ferramenta CLI está pronta! todos-cli agora permite ao usuário adicionar tarefas, visualizá-las e marcá-las como concluídas.

A próxima e última etapa seria publicar sua ferramenta CLI.

Publicando a ferramenta CLI

As ferramentas CLI construídas com Node.js são publicadas em NPM como um pacote. Portanto, você precisa criar uma conta NPM se ainda não tiver uma.

Depois de criar sua conta NPM, em seu terminal no diretório de seu projeto, execute o seguinte:

 npm login

Você será solicitado a inserir seu nome de usuário, senha e e-mail. Se tudo estiver correto, você será conectado.

Em seguida, execute o seguinte comando:

 npm publicar

Este comando publicará sua ferramenta CLI publicamente no npm. Você pode obter um erro se existir outro pacote com o mesmo nome. Nesse caso, você precisará alterar o nome do pacote em package.json

"nome":"PACKAGE_NAME",

Lembre-se de que PACKAGE_NAME é diferente do nome que usamos para os comandos CLI. PACKAGE_NAME é usado para a instalação da ferramenta em sua máquina, mas o nome que você especifica como uma chave em bin é aquele usado para acessar a ferramenta a partir do terminal.

Se nenhum outro pacote no npm tiver o mesmo nome, seu pacote será público e estará disponível para uso! Para instalá-lo, execute:

 npm i-g 

é o nome que você escolheu para o pacote. Observe que se você já executou npm i-g no pacote durante o desenvolvimento, provavelmente é melhor removê-lo antes de instalar o pacote publicado usando npm remove-g na ferramenta diretório.

Atualizando um pacote de ferramentas CLI

Se mais tarde você precisar atualizar seu pacote de ferramentas CLI, poderá fazê-lo com o seguinte comando:

 versão npm 

pode ser um dos seguintes:

  1. patch : uma pequena mudança. Isso aumentará apenas o último número da versão. Isso geralmente é usado para corrigir bugs ou fazer pequenas reparações que não devem afetar a sintaxe de uso do usuário final de sua ferramenta ou pacote
  2. menor : uma pequena alteração. Isso aumentará o segundo número da versão. Isso geralmente é usado para pequenas alterações em seu pacote ou ferramenta, talvez adicionando funcionalidades, mas mantendo as antigas intactas
  3. major : uma grande mudança. Isso aumentará o primeiro número da versão. Isso geralmente é usado para grandes mudanças em seu pacote ou ferramenta que podem afetar o uso do usuário final

Você pode clicar aqui para ler mais sobre controle de versão.

Conclusão

Parabéns, você aprendeu a criar uma ferramenta CLI usando Node.js. As possibilidades são infinitas, então crie algo incrível!

A postagem Criando uma ferramenta CLI com Node.js apareceu primeiro no LogRocket Blog .