Herramientas CLI
Reglas
- Commander.js para análisis de argumentos —
.command(), .option(), .argument() con descripciones
- Indicadores interactivos:
@clack/prompts (moderno, atractivo) o inquirer — para entrada de usuario cuando no se proporcionan argumentos
- Spinners:
ora para operaciones de larga duración — iniciar, actualizar mensaje, finalizar con éxito/fallo
- Colores:
chalk para salida con estilo — usar con moderación: rojo para errores, verde para éxito, amarillo para advertencias, atenuado para información secundaria
- Archivos de configuración:
cosmiconfig — busca automáticamente .toolrc, .toolrc.json, tool.config.js, clave en package.json
- Códigos de salida:
0 para éxito, 1 para error general, 2 para error de uso — siempre usar process.exit(code)
- Banderas
--help y --version: Commander las añade automáticamente — escribir descripciones claras para cada opción
- Manejo de errores: capturar errores en el nivel superior, mostrar mensaje amigable al usuario en stderr, salir con código 1
- Publicación: campo
bin en package.json, shebang #!/usr/bin/env node, "type": "module" para ESM
Patrones
#!/usr/bin/env node
import { Command } from "commander";
import * as p from "@clack/prompts";
import ora from "ora";
import chalk from "chalk";
const program = new Command()
.name("my-tool")
.description("Un CLI que hace cosas útiles")
.version("1.0.0");
program
.command("init")
.description("Inicializar un nuevo proyecto")
.option("-t, --template <name>", "plantilla a usar", "default")
.action(async (options) => {
p.intro(chalk.bold("Configuración del Proyecto"));
const name = await p.text({ message: "¿Nombre del proyecto?", placeholder: "mi-app" });
if (p.isCancel(name)) { p.cancel("Cancelado."); process.exit(0); }
const spinner = ora("Creando proyecto...").start();
await createProject(name, options.template);
spinner.succeed(chalk.green("¡Proyecto creado!"));
p.outro(`Ejecuta ${chalk.cyan(`cd ${name} && npm install`)}`);
});
program.parse();
Evitar
- Imprimir errores en stdout — usar
console.error() o process.stderr.write() para errores
- Salir sin código en caso de fallo — siempre usar
process.exit(1) para que los scripts y CI detecten el fallo
- Muros de texto sin formato — usar colores de chalk, secciones y espacios en blanco para legibilidad
- Requerir entrada interactiva sin alternativa con bandera — siempre soportar sobrescrituras con
--flag para CI/scripting
- Olvidar el shebang
#!/usr/bin/env node — sin él, npx y las instalaciones globales no funcionarán