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

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

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:

mkdir meu-cli
cd meu-cli
npm init -y
npm install commander inquirer chalk

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

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

#!/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);

No package.json, adicione o ponto de entrada do seu CLI:

{
  "name": "meu-cli",
  "version": "1.0.0",
  "bin": {
    "meu-cli": "./bin/cli.js"
  }
}

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

Integrando Commander para Subcomandos

Estrutura avançada com subcomandos

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

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 };

Agora, no bin/cli.js, estenda com mais comandos:

#!/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();
}

Prompts Interativos com Inquirer

Padrões profissionais de interação

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

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;
}

Combine isso com validação e tratamento de erros robusto:

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);
  }
}

Padrões Profissionais e Boas Práticas

Escalabilidade e manutenibilidade

Um CLI profissional deve ser modular. Separe responsabilidades em diferentes arquivos:

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

Crie um arquivo lib/prompts.js para centralizar questões:

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 };

Depois, reutilize em seus comandos:

const { questions } = require('./prompts');

async function createProject(name, options) {
  const answers = await inquirer.prompt([
    { ...questions.selectTemplate },
    { ...questions.confirmInstall }
  ]);

  // Sua lógica aqui
}

Adicione tratamento de erros global e feedback visual consistente usando chalk:

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 };

Conclusão

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

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.

Referências


Artigos relacionados