<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('commander');
const { createProject } = require('../lib/commands');
const { version } = require('../package.json');
program
.version(version)
.description('CLI para gerenciar projetos');
program
.command('create <name>')
.description('Criar um novo projeto')
.action((name) => {
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">{
"name": "meu-cli",
"version": "1.0.0",
"bin": {
"meu-cli": "./bin/cli.js"
}
}</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('inquirer');
const chalk = require('chalk');
const fs = require('fs').promises;
const path = require('path');
async function createProject(name) {
try {
const answers = await inquirer.prompt([
{
type: 'list',
name: 'template',
message: 'Escolha um template:',
choices: ['React', 'Vue', 'Express']
},
{
type: 'confirm',
name: 'git',
message: 'Inicializar repositório Git?',
default: true
}
]);
const projectPath = path.join(process.cwd(), name);
await fs.mkdir(projectPath, { recursive: true });
console.log(chalk.green(✓ Projeto "${name}" criado com sucesso!));
console.log(chalk.blue(Template: ${answers.template}));
if (answers.git) {
console.log(chalk.green('✓ Git iniciado'));
}
} catch (error) {
console.error(chalk.red('✗ Erro ao criar projeto:'), 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('commander');
const { createProject, listProjects, deleteProject } = require('../lib/commands');
const { version } = require('../package.json');
program
.version(version)
.description('Gerenciador profissional de projetos');
program
.command('create <name>')
.description('Criar novo projeto')
.option('-t, --template <type>', 'especificar template', 'React')
.action((name, options) => createProject(name, options));
program
.command('list')
.alias('ls')
.description('Listar projetos')
.action(() => listProjects());
program
.command('delete <name>')
.description('Deletar projeto')
.option('-f, --force', 'deletar sem confirmação')
.action((name, options) => deleteProject(name, options));
program
.command('config')
.description('Configurar CLI')
.action(() => {
console.log(chalk.cyan('Abrindo configurações...'));
});
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: 'checkbox',
name: 'features',
message: 'Quais dependências deseja instalar?',
choices: [
{ name: 'ESLint', checked: true },
{ name: 'Prettier', checked: true },
{ name: 'Jest', value: 'jest' },
{ name: 'Husky', value: 'husky' }
]
},
{
type: 'password',
name: 'apiKey',
message: 'Insira sua chave de API:',
mask: '*'
},
{
type: 'input',
name: 'author',
message: 'Nome do autor:',
default: 'Seu Nome',
validate: (input) => input.length > 0 || 'Campo obrigatório'
},
{
type: 'number',
name: 'port',
message: 'Porta do servidor:',
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('novo-app');
console.log(chalk.green('\n✓ Configuração concluída!'));
console.log(chalk.gray(JSON.stringify(config, null, 2)));
return config;
} catch (error) {
if (error.isTtyError) {
console.error(chalk.red('CLI requer ambiente TTY interativo'));
} else {
console.error(chalk.red('Erro na configuração:'), 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: 'input',
name: 'name',
message: 'Nome do projeto:',
validate: (input) => /^[a-z0-9-]+$/.test(input) || 'Use apenas letras minúsculas, números e hífens'
},
selectTemplate: {
type: 'list',
name: 'template',
message: 'Escolha um template:',
choices: ['React', 'Vue', 'Express', 'Next.js']
},
confirmInstall: {
type: 'confirm',
name: 'install',
message: 'Instalar dependências agora?',
default: true
}
};
module.exports = { questions };</code></pre>
<p>Depois, reutilize em seus comandos:</p>
<pre><code class="language-javascript">const { questions } = require('./prompts');
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>