Embora uma interface gráfica de usuário (GUI) seja vista com mais frequência pelos usuários finais, uma interface de linha de comando (CLI) pode ser uma adição incrivelmente valiosa para ferramentas de desenvolvedor e projetos rápidos em geral.

Às vezes, uma CLI oferece uma experiência ainda melhor do que uma GUI.

Nesta postagem, veremos como criar um Node CLI para verificar notícias esportivas usando dados de ESPN.com . Veremos algumas ferramentas e bibliotecas de código aberto que são úteis para criar uma CLI que executa funções como colorir, carregar spinners e desenhar caixas em torno da saída. Também usaremos uma construção async/await para esperar que os usuários respondam aos prompts que criamos em nossa CLI. Vamos mergulhar!

Configurando o projeto

Observação: se preferir pular a maior parte da configuração, você pode clonar este repositório , execute npm install e comece a adicionar o código a index.js.

Para iniciar nosso projeto, vamos prosseguir e criar um novo diretório, inicializar um pacote npm e criar um arquivo index.js onde escreveremos todo o nosso código:

 mkdir my-cli
cd my-cli
npm init-y
toque em index.js

A partir daqui, vamos instalar os pacotes que usaremos em nossa CLI:

 npm install--save enquirer boxen ora giz node-localstorage @ rwxdev/espn

Como usaremos instruções import em nosso script, precisamos nos certificar de marcar nosso pacote como um módulo . Adicione a seguinte linha em seu arquivo package.json :

"tipo":"módulo",

Primeiro, vamos configurar o arquivo index.js . Vamos configurar uma função principal onde podemos eventualmente executar algum código assíncrono e o resto de nossa CLI. Podemos importar cada uma de nossas dependências à medida que as usamos ao longo do caminho:

//index.js const runCli=async ()=> { //Bem-vindo usuário console.log ("Obrigado por consumir manchetes de esportes com responsabilidade!"); console.log ("Obrigado por usar o ESPN cli!"); Retorna;
} runCli ();

Você pode executar a CLI com node./index.js para ver os logs que acabamos de adicionar.

Obtendo dados de exibição

Agora que definimos nosso esqueleto, vamos adicionar algumas funcionalidades.

Importaremos uma biblioteca para obter as manchetes da ESPN e, em seguida, faremos a chamada inicial para obtê-las. Como esta é uma chamada assíncrona para obter dados, o usuário terá que esperar até que a chamada termine antes de ver qualquer informação. Seria útil indicar ao usuário que a CLI está sendo carregada.

Para fazer isso, usaremos uma biblioteca chamada ora para mostrar um indicador de carregamento animado como este:

Indicador de carregamento animado da biblioteca Ora
Um exemplo do botão giratório de carregamento ora

Antes de fazer nossa chamada assíncrona, vamos iniciar o spinner. Assim que a chamada for concluída, podemos parar o botão giratório:

 importar ora de"ora";
import { getArticleText, getHeadlines, getPageContents, getSports,
} de"@ rwxdev/espn"; const homepageUrl="https://espn.com/"; const runCli=async ()=> { console.log ("Obrigado por consumir manchetes de esportes com responsabilidade!"); const spinner=ora ("Obtendo manchetes..."). start (); const $ homepage=await getPageContents (homepageUrl); spinner.succeed ("títulos ESPN recebidos");
...

Executar a CLI agora com node./index.js mostra um breve spinner/loader enquanto os dados estão sendo buscados. Assim que tivermos os dados, veremos uma marca de seleção. Observe que o texto muda para o que passamos para a função .succeed . Muito legal!

Usando os dados da $ homepage que acabamos de obter, pegaremos todas as manchetes que precisamos exibir aos usuários mais tarde. Novamente, usaremos nossas funções auxiliares aqui:

... spinner.succeed ("títulos ESPN recebidos"); const homepageHeadlines=getHeadlines ($ homepage); const sports=getSports ($ homepage); const headlinesBySport={}; para (deixe esporte de esportes) { getPageContents (sport.href).then (($ sportPage)=> { const headlines=getHeadlines ($ sportPage); headlinesBySport [sport.title]=manchetes; }). catch ((e)=> { console.log ("houve um problema ao obter as manchetes de um determinado esporte", e); }); }
...

Agora, reunimos e armazenamos todas as informações que desejamos exibir aos usuários: esportes e uma grande lista de manchetes para escolher. Usaremos isso mais tarde.

Declarando opções CLI

Em nossa CLI, daremos aos usuários alguns tipos de opções diferentes para escolher: um artigo para ler, um esporte específico para ver as manchetes e um tipo MAIS para ver mais manchetes para um esporte específico.

Esse pode ser um bom candidato para usar o TypeScript porque as seleções se comportam de maneira diferente com base no tipo que são (por exemplo, título, esporte, saída). Estamos apenas usando o JavaScript básico neste tutorial, então faremos alguns tipos de manipulação sozinhos.

Vamos declarar variáveis ​​para que possamos lidar com a entrada do usuário de maneira diferente com base no tipo de seleção. Também faremos algumas opções genéricas para o usuário escolher, independentemente das manchetes disponíveis em um determinado dia.

Inseriremos o seguinte código logo após nosso loop for anterior que adicionamos:

... const selectionTypes={ TÍTULO:"título", ESPORTE:"esporte", Mais mais" }; const genericOptions={ HOMEPAGE_HEADLINES: {title:"veja as manchetes da página inicial"}, LIST_SPORTS: {title:"ver manchetes para esportes específicos", digite: selectionTypes.MORE}, OTHER_SPORTS: {title:"ver manchetes para outros esportes", digite: selectionTypes.MORE}, SAÍDA: {título:"sair"}, };
...

Como esta é uma CLI, solicitaremos informações ao usuário continuamente até que ele opte por sair. Este é um bom ajuste para um loop while no qual podemos especificar a seleção que o usuário fez. Vamos armazenar a seleção e o título da seleção:

... deixe a seleção; let selectionTitle; let articleText; let currentPrompt; deixe sair=falso; enquanto (! sair) { //Onde lidaremos com a seleção do usuário } console.log ("Obrigado por usar o ESPN cli!");
...

Cada vez que o usuário faz uma escolha, armazenamos a seleção, voltamos ao nosso loop e, em seguida, tratamos da seleção que foi feita anteriormente. Há alguns casos diferentes para lidar, então vamos lidar com eles um por um.

Se você fosse executar a CLI agora, o loop while seria infinito porque não dizemos a ele para sair nem permitimos que o usuário forneça dados. Se isso acontecer com você, pressione ctrl + c ou feche a janela do terminal. Com isso atrás de nós, vamos fazer nossa primeira pergunta.

Criação de prompts com Enquirer

Usaremos o Enquirer , uma biblioteca que nos permite mostrar algo no console e aguardar a entrada do usuário. Tem uma API muito simples, como veremos.

Vamos começar importando-o na parte superior do nosso arquivo:

 importar ora de"ora";
importar enquirer de"enquirer";
...

Podemos colocá-lo em uso em nosso loop while criando nosso primeiro prompt para mostrar ao usuário. Ele perguntará ao usuário qual história da página inicial ele gostaria de ler e, em seguida, fornecerá algumas opções:

  • Leia uma das manchetes da página inicial na lista
  • Liste os diferentes esportes para ver mais manchetes
  • Saia do aplicativo
... enquanto (! sair) { if (! selection || selection.title===genericOptions.HOMEPAGE  HEADLINES.title) { currentPrompt=new enquirer.Select ({ nome:"homepage", mensagem:"Que história devemos ler?", escolhas: [... homepageHeadlines.map (item=> item.title), genericOptions.LIST  SPORTS.title, genericOptions.EXIT.title] }); } selectionTitle=await currentPrompt.run (); } ... 

Para o valor choices , fornecemos uma matriz de strings para o usuário escolher. Abaixo deste bloco if é onde realmente executamos o prompt e armazenamos a escolha do usuário como o selectionTitle . Precisaremos de mais informações do que apenas o título, então vamos declarar algumas variáveis ​​que encontrarão a seleção com base no título da seleção:

... selectionTitle=await currentPrompt.run (); const mixedSportHeadlines=Object.values ​​(headlinesBySport).reduce ((acumulador, item)=> { devolver [... acumulador,... item]; }, []) const allOptions=[... Object.values ​​(genericOptions),... homepageHeadlines,... sports,... mixedSportHeadlines]; seleção=allOptions.find (item=> item.title===selectionTitle); } console.log ("Obrigado por usar o ESPN cli!");
...

Essas variáveis ​​basicamente combinam todos os nossos diferentes tipos de opções em um grande conjunto de seleções. A partir dessa matriz allOptions , podemos encontrar aquele com um título correspondente à seleção de nosso usuário.

Agora que estamos lidando com apenas um tipo de seleção, nosso CLI mostrará as manchetes da página inicial original. Este é um passo na direção certa! No entanto, se você passar por isso agora, ficará preso em um loop infinito depois de fazer sua primeira seleção, o que não é divertido.

Saindo da CLI

Vamos lidar com outro tipo de seleção agora: sair. Poderíamos verificar se o usuário selecionou para sair de selection?.Title===genericOptions.EXIT.title , mas também pode ser útil ter isso como o último bit de nosso if else bloqueia para que possamos nos livrar de nossos loops infinitos antes de lidarmos com todos os nossos outros tipos de seleção. Adicionaremos nosso bloco de saída no final de nosso loop while :

... enquanto (! sair) { if (! selection || selection.title===genericOptions.HOMEPAGE_HEADLINES.title) { currentPrompt=new enquirer.Select ({ nome:"homepage", mensagem:"Que história devemos ler?", escolhas: [... homepageHeadlines.map (item=> item.title), genericOptions.LIST_SPORTS.title, genericOptions.EXIT.title] }); } senão { saída=verdadeiro; pausa; }
...

Agora, se o usuário optar por sair, ou se escolher uma seleção que ainda não foi tratada, a CLI será encerrada imediatamente. Chega de loops infinitos!

Lidar com outras seleções do usuário

Já estamos exibindo as manchetes das páginas iniciais, então agora também podemos mostrar as manchetes de outros esportes, se for assim que o usuário escolher.

Vamos adicionar outro bloco para lidar com isso:

... else if (selection.type===selectionTypes.MORE) { currentPrompt=new enquirer.Select ({ nome:"esportes", mensagem:"Para qual esporte você gostaria de manchetes?", escolhas: sports.map (choice=> choice.title) }); } senão { saída=verdadeiro; pausa; }
...

Depois que o usuário escolhe um esporte específico, queremos que ele veja as manchetes desse esporte. Portanto, faremos algo semelhante a quando mostramos as manchetes da página inicial, mas desta vez, só precisamos delas para o esporte escolhido. Podemos inserir outro bloco if else logo antes de nosso bloco else de onde saímos da CLI:

... else if (selection.type===selectionTypes.SPORT) { const sportHeadlines=headlinesBySport [selection.title]; const sportChoices=sportHeadlines.map (opção=> opção.title); currentPrompt=new enquirer.Select ({ nome:"sportHeadlines", mensagem: `Selecione um título $ {selection.title} para obter o texto do artigo`, escolhas: [... sportChoices, genericOptions.HOMEPAGE_HEADLINES.title, genericOptions.OTHER_SPORTS.title, genericOptions.EXIT.title] }); } senão { saída=verdadeiro; pausa; }
...

Logs de estilização com boxen

Por último, podemos realmente mostrar o texto do artigo se um usuário escolher um título para ler. Como este é um artigo, podemos adicionar um pouco de estilo a ele, para que não seja apenas um texto simples e antigo.

Primeiro, importe uma biblioteca chamada boxen na parte superior do arquivo:

 importar ora de"ora";
importar enquirer de"enquirer";
importar boxen de"boxen";
...

Isso nos permitirá desenhar uma caixa ao redor do texto do artigo que mostramos. Insira mais um if else antes de nosso bloco final else . Faremos logout de algum texto usando o estilo do boxen antes de mostrar outro prompt:

 else if (selection.type===selectionTypes.HEADLINE) { articleText=aguarda getArticleText (selection.href); console.log (boxen (selection.href, {borderStyle:'bold'})); console.log (boxen (articleText, {borderStyle:'singleDouble'})); currentPrompt=new enquirer.Select ({ nome:"artigo", mensagem:"Terminou a leitura? E a seguir?", escolhas: [genericOptions.HOMEPAGE_HEADLINES.title, genericOptions.LIST_SPORTS.title, genericOptions.EXIT.title] }); articleText=""; } senão {
...

Agora, a CLI permite que os usuários vejam as manchetes da primeira página, uma lista de esportes, manchetes de esportes específicos e leiam artigos para as manchetes. Isso está parecendo muito bom! Temos dois toques finais a adicionar antes de terminar.

Solicitações de limpeza do Enquirer

Se você examinar a CLI algumas vezes, perceberá que os prompts meio que se amontoam. Eles permanecem no console, mas não oferecem muito valor lá. Como sempre armazenamos o prompt, podemos usar o Enquirer para limpá-lo. No início de nosso loop while , podemos chamar .clear () em currentPrompt . Usaremos encadeamento opcional para que não haja erros na primeira execução:

... enquanto (! sair) { currentPrompt?.clear ();
...

A última coisa que adicionaremos à nossa CLI é um registro no início, informando aos usuários quantas vezes eles usaram a CLI em um dia. Isso será útil porque geralmente não é um CLI produtivo, então isso o ajudará a saber como você está distraído. Talvez você perceba que deveria se concentrar mais em seu trabalho. 🤷‍♂️

Para CLIs mais úteis, como ferramentas de desenvolvedor, esse tipo de recurso também pode mostrar como a ferramenta é valiosa ou indispensável. Então, é claro, se você perceber que está executando o mesmo script 100 vezes por dia, isso pode indicar que você deve incorporar sua funcionalidade em algo que seja executado por conta própria, sem intervenção humana.

Adicionando cor aos registros do console

Para fazer esta parte, vamos armazenar uma contagem de execuções diárias usando um node-localstorage módulo, que importamos aqui. Isso se comportará como Window.localstorage , para que possamos usar uma API semelhante. Também poderíamos usar um banco de dados aqui, se necessário, mas isso deve funcionar muito bem.

Dependendo de quantas vezes executamos a CLI, queremos mostrar nosso log em uma cor diferente, portanto, importaremos nossa última biblioteca, C halk , para mudar a cor do nosso texto:

 importar ora de"ora";
importar enquirer de"enquirer";
importar boxen de"boxen";
importar {LocalStorage} de"node-localstorage";
const localStorage=new LocalStorage ("./scratch");//scratch é o nome do diretório onde o armazenamento local é salvo, pode ser alterado para o que você quiser
importar giz de"giz";
...

Agora, podemos fazer uma nova função onde lidaremos com toda a lógica de uso diário, usando Chalk para alterar a cor do log e armazenamento local para contar o uso diário. Vamos chamá-lo no início de nossa função principal:

...
const showTodaysUsage=()=> { const dateOptions={ano:"numérico", mês:"numérico", dia:"numérico"}; const agora=nova data (); const dateString=now.toLocaleString ("en-US", dateOptions); const todaysRuns=parseInt (localStorage.getItem (dateString)) || 0; const gizColor=todaysRuns <5?"verde": todaysRuns> 10?"vermelho amarelo"; console.log (giz \ [gizColor \] (`Vezes que você verificou a ESPN hoje: $ {todaysRuns}`)); localStorage.setItem (dateString, todaysRuns + 1);
} const runCli=async ()=> { showTodaysUsage (); console.log ("Obrigado por consumir manchetes de esportes com responsabilidade!");
...

Revisão do produto final

Agora, estamos tratando de todos os casos usando todas as bibliotecas que instalamos. Seu script final deve ser semelhante a este:

 importar ora de"ora";
importar enquirer de"enquirer";
importar boxen de"boxen";
importar {LocalStorage} de"node-localstorage";
const localStorage=new LocalStorage ("./scratch");
importar giz de"giz";
import { getArticleText, getHeadlines, getPageContents, getSports,
} de"@ rwxdev/espn";
const homepageUrl="https://espn.com/";
const showTodaysUsage=()=> { const dateOptions={ano:"numérico", mês:"numérico", dia:"numérico"}; const agora=nova data (); const dateString=now.toLocaleString ("en-US", dateOptions); const todaysRuns=parseInt (localStorage.getItem (dateString)) || 0; const gizColor=todaysRuns <5?"verde": todaysRuns> 10?"vermelho amarelo"; console.log (giz \ [gizColor \] (`Vezes que você verificou a ESPN hoje: $ {todaysRuns}`)); localStorage.setItem (dateString, todaysRuns + 1);
}
const runCli=async ()=> { showTodaysUsage (); console.log ("Obrigado por consumir manchetes de esportes com responsabilidade!"); const spinner=ora ("Obtendo manchetes..."). start (); const $ homepage=await getPageContents (homepageUrl); spinner.succeed ("títulos ESPN recebidos"); const homepageHeadlines=getHeadlines ($ homepage); const sports=getSports ($ homepage); const headlinesBySport={}; para (deixe esporte de esportes) { getPageContents (sport.href).then (($ sportPage)=> { const headlines=getHeadlines ($ sportPage); headlinesBySport [sport.title]=manchetes; }). catch ((e)=> { console.log ("houve um problema ao obter as manchetes de um determinado esporte", e); }); } const selectionTypes={ TÍTULO:"título", ESPORTE:"esporte", Mais mais" }; const genericOptions={ HOMEPAGE_HEADLINES: {title:"veja as manchetes da página inicial"}, LIST_SPORTS: {title:"ver manchetes para esportes específicos", digite: selectionTypes.MORE}, OTHER_SPORTS: {title:"ver manchetes para outros esportes", digite: selectionTypes.MORE}, SAÍDA: {título:"sair"}, }; deixe a seleção; let selectionTitle; let articleText; let currentPrompt; deixe sair=falso; enquanto (! sair) { currentPrompt?.clear (); if (! selection || selection.title===genericOptions.HOMEPAGE_HEADLINES.title) { currentPrompt=new enquirer.Select ({ nome:"homepage", mensagem:"Que história devemos ler?", escolhas: [... homepageHeadlines.map (item=> item.title), genericOptions.LIST_SPORTS.title, genericOptions.EXIT.title] }); } else if (selection.type===selectionTypes.MORE) { currentPrompt=new enquirer.Select ({ nome:"esportes", mensagem:"Para qual esporte você gostaria de manchetes?", escolhas: sports.map (choice=> choice.title) }); } else if (selection.type===selectionTypes.SPORT) { const sportHeadlines=headlinesBySport [selection.title]; const sportChoices=sportHeadlines.map (opção=> opção.title); currentPrompt=new enquirer.Select ({ nome:"sportHeadlines", mensagem: `Selecione um título $ {selection.title} para obter o texto do artigo`, escolhas: [... sportChoices, genericOptions.HOMEPAGE_HEADLINES.title, genericOptions.OTHER_SPORTS.title, genericOptions.EXIT.title] }); } else if (selection.type===selectionTypes.HEADLINE) { articleText=aguarda getArticleText (selection.href); console.log (boxen (selection.href, {borderStyle:'bold'})); console.log (boxen (articleText, {borderStyle:'singleDouble'})); currentPrompt=new enquirer.Select ({ nome:"artigo", mensagem:"Terminou a leitura? E a seguir?", escolhas: [genericOptions.HOMEPAGE_HEADLINES.title, genericOptions.LIST_SPORTS.title, genericOptions.EXIT.title] }); articleText=""; } senão { saída=verdadeiro; pausa; } selectionTitle=await currentPrompt.run (); const mixedSportHeadlines=Object.values ​​(headlinesBySport).reduce ((acumulador, item)=> { devolver [... acumulador,... item]; }, []) const allOptions=[... Object.values ​​(genericOptions),... homepageHeadlines,... sports,... mixedSportHeadlines]; seleção=allOptions.find (item=> item.title===selectionTitle); } console.log ("Obrigado por usar o ESPN cli!"); Retorna;
} runCli ();

Você também pode encontrar a aparência do código aqui .

Conclusão

Tudo pronto! Nosso CLI nos mostrará as manchetes da página inicial da ESPN, listará os esportes disponíveis e nos permitirá ler artigos. Fornecemos prompts para o usuário, exibimos um botão giratório animado durante o carregamento, saída colorida do console e caixas desenhadas ao redor de algum texto do console.

Nós nos familiarizamos com alguns utilitários Node CLI diferentes, mas existem vários outros que você pode explorar e brincar. As interfaces de linha de comando podem ser extremamente úteis para a produtividade e podem ser simplesmente divertidas! Brinque com o que fizemos neste tutorial e veja como você pode transformá-lo em algo que seja útil para você!

A postagem Criando um Node CLI apareceu primeiro em LogRocket Blog .

Source link