Criando uma ferramenta de CLI com NodeJS

Ferramentas de linha de comando ajudam quando você precisa executar tarefas mais rapidamente. Que tal aprender a criar você mesmo a sua? E em NodeJS! E ainda disponibilizar no NPMJS! Ficou curioso? Vem que eu te mostro como faz ;)

O que é uma CLI?

CLI significa Command Line Interface, ou seja, é uma ferramenta que disponibiliza uma interface de linha de comando para que você possa executar alguns comandos específicos no terminal. Normalmente essas ferramentas são criadas utilizando shell script, mas nós vamos criar a nossa com Javascript :D

Antes de começar, que tal mostrar alguns exemplos de CLI?

Gulp, Grunt, Express, Mocha, e inclusive o próprio NodeJS e NPM são ótimos exemplos de CLI. Essas ferramentas de CLI criadas em NodeJS normalmente estão disponíveis para baixar no https://www.npmjs.com/, onde você instala com o comando npm i -g nome-da-ferramenta.

Quando você instala um módulo do Node globalmente, usando o -g, é porque essa ferramenta normalmente disponibiliza essa interface de linha de comando, onde você não precisa depender do comando node nome-da-ferramenta para executá-la, e sim poder usar o próprio nome da ferramenta como comando.

Por exemplo: como você faz para executar uma tarefa do Gulp? Você executa gulp nome-da-task, sem precisar do comando node! Isso é uma CLI :D

Como faz então?

Vamos criar uma ferramenta bastante simples: uma calculadora que faz a soma entre dois números passados como parâmetro. A primeira coisa é escolher um nome para a ferramenta, - vixxxxxx - pois esse nome precisa estar disponível no NPMJS :)

Acho que essa é a parte mais difícil, haha :D

Não conheço nenhuma ferramenta que verifique se um nome está disponível no NPMJS - inclusive, isso seria uma ideia bem útil de CLI ;) - então vamos verificar manualmente se tem algum nome disponível, chutando mesmo:

Acesse https://www.npmjs.com/package/, e coloque um nome após a barra. Se ele estiver disponível, você verá uma página de not found :)

Nossa CLI se chamará calculatr, e será usada da seguinte forma: usaremos no terminal o comando para somar dois números:

1
calculatr sum 2 3

Que nos dará o resultado 5! Bem inútil, mas vai servir para você ver como funciona uma CLI :D

Observação: existem muitos pacotes no NPMJS, alguns indispensáveis, outros bastante inúteis. Então lembre-se que você sempre terá a opção de despublicar um pacote, se você estiver fazendo somente testes para ver como tudo funciona por lá. Assim nós ajudamos ao próximo e a nós mesmos, não deixando no NPMJS coisas desatualizadas e que não sirvam para nada ;)

Agora que sabemos como será a interface do nosso projeto, vamos ver como criar a ferramenta.

Primeiros passos

O passo inicial é criar um diretório separado para o projeto. A estrutura será bastante simples: só precisaremos de um diretório chamado calculatr/ e, dentro dele, um arquivo com qualquer nome. Vamos chamar de calculatr.js :)

Precisaremos também de um package.json com algumas informações para o nosso módulo.

Em artigos anteriores, mostrei que você poderia sempre criar o seu package.json com o comando echo "{}" > package.json, que imprimiria dentro do arquivo somente um “abre e fecha chaves”, que é o mínimo que precisamos quando estamos usando o Node com um task runner, por exemplo, onde o package.json será usado somente para gerenciamento de pacotes.

Agora, nós vamos criar nosso próprio módulo NodeJS, então o package.json precisa de uma atenção especial :)

Criando o package.json

Para criar o package.json, você pode usar o comando npm init, e então responder algumas perguntas para criar seu módulo. A primeira pergunta é sobre o nome do módulo. Por default, NPM vai te sugerir o nome do diretório atual, que normalmente é o nome do projeto. Vamos manter (se estive como calculatr).

A próxima pergunta é sobre a versão. Estamos começando agora, então vamos deixar com a versão 0.0.1. Daqui a pouco vou explicar melhor sobre como funcionam as versões ;)

Por enquanto, digite 0.0.1 e dê enter.

Agora é pedido uma descrição sobre o projeto. Essa descrição aparecerá no site do NPMJS, então capriche ;) Vamos deixar algo como:

1
CLI Calculator

Depois, ele pede o entrypoint. Esse parâmetro referencia o arquivo principal, ou o ponto de entrada da nossa aplicação. No nosso caso, esse arquivo é o calculatr.js.

O próximo é test command. Esse será o comando que você irá utilizar para testar seu código. Vamos utilizar o comando:

1
istanbul cover _mocha -- -R spec

Utilizaremos o istanbul para coverage e o mocha para os testes. Lembre-se de ter esses dois pacotes instalados globalmente. Se não tiver, execute no seu terminal:

1
npm i -g istanbul mocha

A próxima pergunta é sobre o repositório no Github. Se você ainda não criou o repositório para esse projeto, faça-o e entre com a URL. Se quiser fazer depois, não há problemas. Quando você configurar o git para esse projeto e rodar npm init novamente, o seu package.json será atualizado com essas informações :)

A licença, você pode escolher uma que esteja de acordo com o formato que você irá disponibilizar sua ferramenta. Normalmente usa-se MIT, ou GPLv2, v3. Se tiver dúvidas quanto à licença, pode escolher uma [por aqui] (http://choosealicense.com/).

Se tudo der certo, você terá um package.json preenchido!

Mas, como estamos criando uma ferramenta de CLI, precisamos adicionar mais algumas entradas. Edite seu package.json, e adicione:

1
2
3
4
"preferGlobal": true,
"bin": {
"calculatr": "calculatr.js"
}

No site do NPMJS, é mostrado o comando de instalação do módulo. O preferGlobal diz ao NPM que esse módulo deve ser instalado globalmente. Logo, no site do NPMJS irá aparecer o seguinte comando para instalação do móulo:

1
npm install -g calculatr

E o parâmetro bin, é para que possamos dizer ao NPM que, quando executarmos calculatr no terminal, o arquivo que será chamado é o calculatr.js.

Adicione também mais uma entrada em scripts, no seu package.json. Ele deve ficar assim:

1
2
3
4
"scripts": {
"test": "istanbul cover _mocha -- -R spec",
"watch": "istanbul cover _mocha -- -R spec --watch"
},

Para que possamos executar nossos testes sem precisar nos preocupar em ficar executando o comando npm test toda hora. Só precisaremos executar npm run watch e voilà!

E agora nosso package.json está pronto!

Sobre Versões

As versões para os módulos funcionam mais ou menos assim: o terceiro número se refere somente a ajustes, solução de bugs, mas sem adicionar novas funcionalidades. Quando temos um módulo com versão 0.0.x, onde x você incrementa a cada alteração que você fizer no módulo, chamamos de versão alpha. O segundo número, é quando você tem uma versão de algo já funcional. Então, a versão 0.1.0 tem a primeira versão beta, ou seja, ainda em testes, mas com alguma funcionalidade pronta. O incremento do segundo número deve ser a cada nova funcionalidade. O terceiro número continua sendo para resolver bugs, ou pequenos problemas. Então, se você tiver uma versão 0.1.3, significa que, na versão beta, que tem 1 funcionalidade que funciona (?), você já fez 3 correções de bugs, ou ajustes.

E o último número, é quando você tem uma versão estável do módulo, com o mínimo de funcionalidades que você considera ideal para que ele seja um módulo “completo”. Esse completo é muito relativo, mas, dando o exemplo do nosso módulo, quando ele estiver somando dois números, ele já estará estável, e poderemos usar a versão 1.0.0.

Nem todos os módulos seguem essa ideia. Alguns tem sua própria forma de gerenciar suas versões, mas a base é mais ou menos essa.

Criando o módulo

Agora, vamos começar a criar nosso módulo!

Como boa prática, vamos primeiro aos testes :D

Crie um diretório test, e, dentro dele, um arquivo chamado test.calculatr.js. Antes de começar, vamos instalar as dependências que usaremos. Já temos o istanbule o mocha, como citado anteriormente. Se não os tiver instalados globalmente ainda, instale.

Após isso, vamos às dependências para os testes:

1
npm i --save-dev should

Instalamos com –save-dev, pois queremos o should somente para desenvolvimento, ele não fará parte da aplicação.

E então vamos escrever nosso primeiro teste. No arquivo test/test.calculatr.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'use strict';

var exec = require( 'child_process' ).exec;
var pkg = require( '../package.json' );
var calculatr = './calculatr.js';
require( 'should' );

describe( 'Calculatr', function() {
it( 'Should return version of calculatr', function( done ) {
exec( calculatr + ' --version', function( err, stdout, stderr ) {
if( err ) throw err;
stdout.replace( '\n', '' ).should.be.equal( pkg.version );
done();
});
});
});

Opa! Tem bastante coisa nova ae!

Sim, vou explicar tudo :D

O require( 'child_process' ).exec faz parte do NodeJS. Ele vai nos ajudar a executar comandos no terminal, permitindo-nos usar uma função de callback para fazer nosso teste quando o comando for executado :)

Na linha 4, adicionamos o nosso package.json, e já veremos o porquê!

Na linha 5, vamos simular o comando para executar o calculatr, pois ainda não temos o módulo instalado globalmente. Mas quando ele for instalado, usando calculatr irá chamar o arquivo calculatr.js :)

Adicionamos o should para fazer os testes.

No primeiro teste, vamos verificar se nossa CLI retorna a versão correta. Se usarmos o comando calculatr --version, deveria retornar a versão. Na linha 10 você pode ver o teste sendo feito, usando o exec para dar o comando.

O que ele faz é bem simples: como primeiro parâmetro da função, passamos o comando calculatr + ' --version'. A função de callback nos retorna, como primeiro parâmetro, um erro (se houver), depois a saída no terminal (stdout), ou algum erro para a saída (stderr).

O que iremos usar para testar é a saída (stdout). Fazemos o teste com o comando da linha 12. Testamos se a saída - que retorna sempre uma quebra de linha no final, por isso o replace - é igual à versão do nosso projeto. Pegamos a versão diretamente do package.json, pois ela será atualizada constantemente. Se colocarmos manualmente, teremos que lembrar de alterar a cada versão lançada, para não quebrar nosso teste.

Usamos a função done(), que já vimos no artigo sobre como criar um módulo Ajax com Promises, que serve para dizer à função it(), que é o nosso teste, que nossa asserção já foi feita, e ele pode verificar se passou ou não.

Obviamente, nosso teste quebra, pois ainda não temos nada no arquivo principal :)

Ainda antes de criar nosso módulo, você percebeu que no nosso teste não executamos o comando node ./calculatr.js --version, e sim ./calculatr.js --version, sem o node?

Como essa é uma ferramenta de CLI, ela precisa ser executada sem o comando node, mas para isso, ela precisa de permissão de execução. Para dar essa permissão, execute no seu terminal, na raiz do projeto:

1
chmod +x calculatr.js

Isso fará com que seja possível executar esse arquivo diretamente com o comando ./calculatr! Mas ainda tem outro segredo que só vou contar quando escrevermos nosso módulo :P

Vamos fazê-lo agora! No arquivo calculatr.js, adicione:

1
2
3
4
5
6
7
8
#!/usr/bin/env node
'use strict';

var program = require( 'commander' );
var pkg = require( './package.json' );

program.version( pkg.version );
program.parse( process.argv );

A primeira linha é essencial para nossa CLI, pois é ela que faz a mágica para que nosso módulo funcione sem precisar escrever em shell script: dizemos que, ao chamar esse arquivo - executando ./calculatr no terminal - , o programa que irá executá-lo será o node!

Então, por baixo dos panos, ao executarmos ./calculatr, na verdade, o comando executado será node ./calculatr ;)

Safadeeeenho! Mas pera, tem um módulo novo ali! O que é esse commander?

O commander nos ajuda a montar uma interface para usarmos na linha de comando. Você deve instalá-lo com o comando:

1
npm i --save commander

Agora usamos o --save, pois esse módulo será uma dependência do nosso projeto. Toda vez que o baixarmos, também será baixado o commander :)

Na linha 7, passamos para o commander a versão do nosso projeto. Pegamos direto do package.json, como fizemos no arquivo de teste. E na linha 8, fazemos o parse com os argumentos que forem passados no terminal. O process.argv recebe uma coleção com os argumentos passados. Ao fazer o parse, podemos executar o comando:

1
./calculatr.js --version

Que retornará a versão da nossa ferramenta.

Agora, salvando o arquivo e olhando para o nosso teste, ele passou!! \o/

Primeiro método: soma (sum)

Vamos fazer o primeiro método da nossa calculadora: a soma. Vamos receber, inicialmente, dois valores e somá-los.

E vamos usar o comando abaixo para efetuar a soma:

1
calculatr sum 1 2

Que deverá imprimir o resultado 3. Vamos fazer o teste? No arquivo test/test.calculatr.js, adicine mais um teste:

1
2
3
4
5
6
7
it( 'Command "calculatr sum 1 2" Should return 3', function( done ) {
exec( calculatr + ' sum 1 2', function( err, stdout, stderr ) {
if( err ) throw err;
Number( stdout ).should.be.equal( 3 );
done();
});
});

O formato desse teste é bem parecido com o anterior. Só mudaremos o comando, que agora é calculatr + ' sum 1 2 ', que deveria retornar 3. O retorno da CLI é sempre uma string, então precisamos converter para número antes de fazer o teste, por isso usamos o objeto Number aqui, e então verificamos se o resultado é 3.

E adivinha: o teste NÃO PASSA! A-ha! ¬¬

Agora vamos fazer a implementação do código. Vamos modificar um pouco o arquivo calculatr.js:

1
2
3
4
5
6
7
8
9
10
program.version( pkg.version );

program
.command( 'sum <number1> <number2>' )
.description( 'Sum two numbers' )
.action(function sum( number1, number2 ) {
console.log( Number( number1 ) + Number( number2 ) );
});

program.parse( process.argv );

Temos três métodos novos do commander: command, description e action. Nós poderíamos ter encadeado todos os métodos, ficando algo como:

1
2
3
4
5
6
7
8
program
.version( pkg.version );
.command( 'sum <number1> <number2>' )
.description( 'Sum two numbers' )
.action(function sum( number1, number2 ) {
console.log( Number( number1 ) + Number( number2 ) );
})
.parse( process.argv );

Se você achar que assim fica mais legível, fique à vontade :)

Separei pois acho que fica melhor deixar os comandos separados, já que poderemos adicionar mais funcionalidades à nossa ferramenta xD

O command recebe como parâmetro uma string, com o comando, e os argumentos que ele suporta. Quando você usa o parâmetro envolto por < e >, quer dizer que o parâmetro é obrigatório. Para parâmetros opcionais, use [ e ].

Depois, o description é uma descrição sobre o comando. Essa descrição é útil quando você executa no terminal calculatr --help, onde será mostrado o help da sua ferramenta. O commander já deixa toda a interface de retorno do help bonitinha, no padrão da maioria dos CLIs em sistemas *nix :D

E a action é a ação para quando o comando for executado no terminal. Essa ação fica em uma função de callback, que recebe como parâmetro os argumentos passados no terminal. O primeiro é o primeiro número, o segundo, o próximo número.

No retorno, executamos um console.log(), para mostrar na saída do terminal, convertendo os dois parâmetros para número com Number() e retornando a soma dos mesmos.

E nosso teste passou \o/

O commander tem vários outros métodos legais. Para saber mais sobre ele, o TJ escreveu um post bem completo, que você pode ler aqui.

E agora, qual o próximo passo?

O próximo passo é subir nossa ferramenta no https://www.npmjs.com/!

Subindo o módulo no NPMJS

Para subir o módulo é bastante simples: você só precisa executar o comando:

1
npm publish

Mas antes de fazer isso, você precisa ter um cadastro no site do NPM, e configurar sua máquina para poder publicar seus módulos. Vamos ver como fazer isso!

O cadastro é simples: entre em https://www.npmjs.com/, e cadastre-se, como em qualquer outro site xD

Você ainda pode se cadastrar via terminal, com o comando:

1
npm adduser

Será pedido um nome de usuário, uma senha e um e-mail (que ficará público).

Se você já tinha cadastro no site, só precisa então logar no seu ambiente, com o comando:

1
npm login

Após isso, você precisa verificar se os seus dados estão corretamente configurados. Use o comando:

1
npm config ls

Isso irá listar os seus dados. Confira se tudo está correto.

Mas temos alguns arquivos que não queremos enviar para o NPMJS, como o diretório test, o node_modules, o diretório coverage, criado pelo istanbul.

Para isso, podemos criar um arquivo .npmignore na raiz do nosso projeto, e adicionar esses diretórios, como faríamos no .gitignore.

Feito isso, só executar o comando:

1
npm publish

E aguardar! Quando o comando terminar de rodar, acesse o https://www.npmjs.com/package/nome-do-seu-pacote e veja se o seu CLI está lá! Pronto, agora você tem uma ferramenta de CLI que funciona e está publicada no NPMJS!

Vamos testar se ela funciona? Instale-a globalmente, com o comando:

1
[sudo] npm i -g calculatr

Coloquei o sudo, pois provavelmente você irá precisar dele. Tente primeiro instalar sem o sudo. Se não der certo, então use ele.

Agora com nosso módulo instalado, vamos executá-lo para testar, com o comando:

1
calculatr sum 3 4

E o resultado é: 7!

Agora tudo pronto :D

Para ver o código completo desse projeto, você pode acessá-lo no Github ou no NPMJS.

Até a próxima! Dúvidas? Comente :D

Sobre o #1postperday: https://blog.da2k.com.br/2014/12/31/um-post-por-dia/

Tem alguma sugestão para os próximos posts do #1postperday? Deixe ela aqui: https://github.com/fdaciuk/fdaciuk.github.io/issues/1