JavaScript Avançado

Guia Completo de Construindo CLI Profissional em Node.js com commander e inquirer

9 min de leitura

Guia Completo de Construindo CLI Profissional em Node.js com commander e inquirer

Introdução ao Commander e Inquirer Construir interfaces de linha de comando (CLI) profissionais é uma habilidade essencial para desenvolvedores Node.js modernos. As bibliotecas commander e inquirer são o padrão da indústria para este propósito. O commander gerencia argumentos e subcomandos com elegância, enquanto o inquirer torna a interação com usuários intuitiva através de prompts interativos. Juntas, essas ferramentas permitem criar aplicações CLI robustas, mantendo código limpo e escalável. Neste artigo, você aprenderá a arquitetar uma CLI profissional desde o zero, entendendo não apenas a sintaxe, mas os padrões que grandes projetos como o Angular CLI e Create React App utilizam. Vamos ao trabalho. Configuração e Estrutura Base Setup inicial do projeto Comece criando um novo projeto Node.js e instalando as dependências necessárias: O pacote chalk será útil para colorir a saída do terminal, melhorando a experiência do usuário. Crie um arquivo como ponto de entrada. Esta é a convenção padrão em projetos profissionais: No , adicione o ponto de entrada

<h2>Introdução ao Commander e Inquirer</h2>

<p>Construir interfaces de linha de comando (CLI) profissionais é uma habilidade essencial para desenvolvedores Node.js modernos. As bibliotecas <strong>commander</strong> e <strong>inquirer</strong> são o padrão da indústria para este propósito. O commander gerencia argumentos e subcomandos com elegância, enquanto o inquirer torna a interação com usuários intuitiva através de prompts interativos. Juntas, essas ferramentas permitem criar aplicações CLI robustas, mantendo código limpo e escalável.</p>

<p>Neste artigo, você aprenderá a arquitetar uma CLI profissional desde o zero, entendendo não apenas a sintaxe, mas os padrões que grandes projetos como o Angular CLI e Create React App utilizam. Vamos ao trabalho.</p>

<h2>Configuração e Estrutura Base</h2>

<h3>Setup inicial do projeto</h3>

<p>Comece criando um novo projeto Node.js e instalando as dependências necessárias:</p>

<pre><code class="language-bash">mkdir meu-cli

cd meu-cli

npm init -y

npm install commander inquirer chalk</code></pre>

<p>O pacote <strong>chalk</strong> será útil para colorir a saída do terminal, melhorando a experiência do usuário.</p>

<p>Crie um arquivo <code>bin/cli.js</code> como ponto de entrada. Esta é a convenção padrão em projetos profissionais:</p>

<pre><code class="language-javascript">#!/usr/bin/env node

const { program } = require(&#039;commander&#039;);

const { createProject } = require(&#039;../lib/commands&#039;);

const { version } = require(&#039;../package.json&#039;);

program

.version(version)

.description(&#039;CLI para gerenciar projetos&#039;);

program

.command(&#039;create &lt;name&gt;&#039;)

.description(&#039;Criar um novo projeto&#039;)

.action((name) =&gt; {

createProject(name);

});

program.parse(process.argv);</code></pre>

<p>No <code>package.json</code>, adicione o ponto de entrada do seu CLI:</p>

<pre><code class="language-json">{

&quot;name&quot;: &quot;meu-cli&quot;,

&quot;version&quot;: &quot;1.0.0&quot;,

&quot;bin&quot;: {

&quot;meu-cli&quot;: &quot;./bin/cli.js&quot;

}

}</code></pre>

<p>Instale localmente com <code>npm link</code> para testar: <code>meu-cli --version</code> funcionará em qualquer lugar do seu terminal.</p>

<h2>Integrando Commander para Subcomandos</h2>

<h3>Estrutura avançada com subcomandos</h3>

<p>O commander brilha ao gerenciar múltiplos comandos. Crie uma estrutura modular em <code>lib/commands/index.js</code>:</p>

<pre><code class="language-javascript">const inquirer = require(&#039;inquirer&#039;);

const chalk = require(&#039;chalk&#039;);

const fs = require(&#039;fs&#039;).promises;

const path = require(&#039;path&#039;);

async function createProject(name) {

try {

const answers = await inquirer.prompt([

{

type: &#039;list&#039;,

name: &#039;template&#039;,

message: &#039;Escolha um template:&#039;,

choices: [&#039;React&#039;, &#039;Vue&#039;, &#039;Express&#039;]

},

{

type: &#039;confirm&#039;,

name: &#039;git&#039;,

message: &#039;Inicializar repositório Git?&#039;,

default: true

}

]);

const projectPath = path.join(process.cwd(), name);

await fs.mkdir(projectPath, { recursive: true });

console.log(chalk.green(✓ Projeto &quot;${name}&quot; criado com sucesso!));

console.log(chalk.blue(Template: ${answers.template}));

if (answers.git) {

console.log(chalk.green(&#039;✓ Git iniciado&#039;));

}

} catch (error) {

console.error(chalk.red(&#039;✗ Erro ao criar projeto:&#039;), error.message);

process.exit(1);

}

}

module.exports = { createProject };</code></pre>

<p>Agora, no <code>bin/cli.js</code>, estenda com mais comandos:</p>

<pre><code class="language-javascript">#!/usr/bin/env node

const { program } = require(&#039;commander&#039;);

const { createProject, listProjects, deleteProject } = require(&#039;../lib/commands&#039;);

const { version } = require(&#039;../package.json&#039;);

program

.version(version)

.description(&#039;Gerenciador profissional de projetos&#039;);

program

.command(&#039;create &lt;name&gt;&#039;)

.description(&#039;Criar novo projeto&#039;)

.option(&#039;-t, --template &lt;type&gt;&#039;, &#039;especificar template&#039;, &#039;React&#039;)

.action((name, options) =&gt; createProject(name, options));

program

.command(&#039;list&#039;)

.alias(&#039;ls&#039;)

.description(&#039;Listar projetos&#039;)

.action(() =&gt; listProjects());

program

.command(&#039;delete &lt;name&gt;&#039;)

.description(&#039;Deletar projeto&#039;)

.option(&#039;-f, --force&#039;, &#039;deletar sem confirmação&#039;)

.action((name, options) =&gt; deleteProject(name, options));

program

.command(&#039;config&#039;)

.description(&#039;Configurar CLI&#039;)

.action(() =&gt; {

console.log(chalk.cyan(&#039;Abrindo configurações...&#039;));

});

program.parse(process.argv);

if (!process.argv.slice(2).length) {

program.outputHelp();

}</code></pre>

<h2>Prompts Interativos com Inquirer</h2>

<h3>Padrões profissionais de interação</h3>

<p>O inquirer oferece diversos tipos de prompts. Aqui está um exemplo prático que demonstra a variedade:</p>

<pre><code class="language-javascript">async function configurarProjeto(nome) {

const answers = await inquirer.prompt([

{

type: &#039;checkbox&#039;,

name: &#039;features&#039;,

message: &#039;Quais dependências deseja instalar?&#039;,

choices: [

{ name: &#039;ESLint&#039;, checked: true },

{ name: &#039;Prettier&#039;, checked: true },

{ name: &#039;Jest&#039;, value: &#039;jest&#039; },

{ name: &#039;Husky&#039;, value: &#039;husky&#039; }

]

},

{

type: &#039;password&#039;,

name: &#039;apiKey&#039;,

message: &#039;Insira sua chave de API:&#039;,

mask: &#039;*&#039;

},

{

type: &#039;input&#039;,

name: &#039;author&#039;,

message: &#039;Nome do autor:&#039;,

default: &#039;Seu Nome&#039;,

validate: (input) =&gt; input.length &gt; 0 || &#039;Campo obrigatório&#039;

},

{

type: &#039;number&#039;,

name: &#039;port&#039;,

message: &#039;Porta do servidor:&#039;,

default: 3000

}

]);

return answers;

}</code></pre>

<p>Combine isso com validação e tratamento de erros robusto:</p>

<pre><code class="language-javascript">async function setupAndValidate() {

try {

const config = await configurarProjeto(&#039;novo-app&#039;);

console.log(chalk.green(&#039;\n✓ Configuração concluída!&#039;));

console.log(chalk.gray(JSON.stringify(config, null, 2)));

return config;

} catch (error) {

if (error.isTtyError) {

console.error(chalk.red(&#039;CLI requer ambiente TTY interativo&#039;));

} else {

console.error(chalk.red(&#039;Erro na configuração:&#039;), error.message);

}

process.exit(1);

}

}</code></pre>

<h2>Padrões Profissionais e Boas Práticas</h2>

<h3>Escalabilidade e manutenibilidade</h3>

<p>Um CLI profissional deve ser modular. Separe responsabilidades em diferentes arquivos:</p>

<pre><code>projeto/

├── bin/

│ └── cli.js // Ponto de entrada

├── lib/

│ ├── commands/

│ │ ├── index.js

│ │ ├── create.js

│ │ ├── delete.js

│ │ └── list.js

│ ├── prompts.js // Perguntas reutilizáveis

│ ├── utils.js // Funções auxiliares

│ └── config.js // Gerenciamento de config

└── package.json</code></pre>

<p>Crie um arquivo <code>lib/prompts.js</code> para centralizar questões:</p>

<pre><code class="language-javascript">const questions = {

projectName: {

type: &#039;input&#039;,

name: &#039;name&#039;,

message: &#039;Nome do projeto:&#039;,

validate: (input) =&gt; /^[a-z0-9-]+$/.test(input) || &#039;Use apenas letras minúsculas, números e hífens&#039;

},

selectTemplate: {

type: &#039;list&#039;,

name: &#039;template&#039;,

message: &#039;Escolha um template:&#039;,

choices: [&#039;React&#039;, &#039;Vue&#039;, &#039;Express&#039;, &#039;Next.js&#039;]

},

confirmInstall: {

type: &#039;confirm&#039;,

name: &#039;install&#039;,

message: &#039;Instalar dependências agora?&#039;,

default: true

}

};

module.exports = { questions };</code></pre>

<p>Depois, reutilize em seus comandos:</p>

<pre><code class="language-javascript">const { questions } = require(&#039;./prompts&#039;);

async function createProject(name, options) {

const answers = await inquirer.prompt([

{ ...questions.selectTemplate },

{ ...questions.confirmInstall }

]);

// Sua lógica aqui

}</code></pre>

<p>Adicione tratamento de erros global e feedback visual consistente usando <strong>chalk</strong>:</p>

<pre><code class="language-javascript">function logSuccess(message) {

console.log(chalk.green(✓ ${message}));

}

function logError(message) {

console.error(chalk.red(✗ ${message}));

}

function logInfo(message) {

console.log(chalk.blue(ℹ ${message}));

}

module.exports = { logSuccess, logError, logInfo };</code></pre>

<h2>Conclusão</h2>

<p>Você aprendeu como construir uma CLI profissional usando <strong>commander</strong> para gerenciar comandos de forma elegante e <strong>inquirer</strong> para criar interfaces interativas ricas. Os três conceitos-chave são: <strong>(1) Modularizar sua arquitetura</strong> separando comandos, prompts e utilitários em arquivos específicos; <strong>(2) Usar padrões do ecossistema</strong>, como o diretório <code>bin/</code> para entrada e respeitar convenções do npm; <strong>(3) Investir em experiência do usuário</strong> com feedback visual claro e validação robusta de entrada.</p>

<p>Com essa base sólida, você está pronto para construir ferramentas que competem com as melhores CLI do mercado. Pratique criando pequenos projetos e explore as opções avançadas da documentação oficial.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://github.com/tj/commander.js" target="_blank" rel="noopener noreferrer">Commander.js - Official Documentation</a></li>

<li><a href="https://github.com/SBoudrias/Inquirer.js" target="_blank" rel="noopener noreferrer">Inquirer.js - GitHub Repository</a></li>

<li><a href="https://github.com/chalk/chalk" target="_blank" rel="noopener noreferrer">Chalk - Terminal String Styling</a></li>

<li><a href="https://github.com/lirantal/nodejs-cli-apps-best-practices" target="_blank" rel="noopener noreferrer">Node.js CLI Best Practices</a></li>

<li><a href="https://www.pluralsight.com/guides/nodejs-command-line-interfaces" target="_blank" rel="noopener noreferrer">Building Command Line Interfaces with Node.js - Pluralsight</a></li>

</ul>

Comentários

Mais em JavaScript Avançado

Dominando Performance em Node.js: Profiling com --inspect e Clinic.js em Projetos Reais
Dominando Performance em Node.js: Profiling com --inspect e Clinic.js em Projetos Reais

Entendendo a Importância do Profiling em Node.js Performance é mais que uma m...

O que Todo Dev Deve Saber sobre SharedArrayBuffer e Atomics: Memória Compartilhada entre Workers
O que Todo Dev Deve Saber sobre SharedArrayBuffer e Atomics: Memória Compartilhada entre Workers

SharedArrayBuffer: O Que É e Por Que Usar SharedArrayBuffer é um objeto JavaS...

Streams Avançados em Node.js: Transform, Duplex e Backpressure na Prática
Streams Avançados em Node.js: Transform, Duplex e Backpressure na Prática

Streams Avançados em Node.js: Transform, Duplex e Backpressure Streams são um...