Creando nuestro CLI (Command-Line Interface) usando NodeJS

Empezando con nuestro CLI

Hoy en día es muy común encontrar decenas de herramientas para acelerar nuestro desarrollo en la parte de Front-end, ya sea para crear proyectos desde cero e inclusive repetir ciertas tareas repetitivas y monótonas.

En ocasiones nos encontramos situaciones en las que esas herramientas no cubren nuestras necesidades:

  • Ya sea por la naturaleza del proyecto que tiene cierta lógica especial y requiere de ciertas personalizaciones.
  • El proyecto no tiene necesidades especiales pero tenemos que realizar ciertos procesos repetitivos que se acaban convirtiendo en un copy/paste, con lo que eso supone.
  • También nos podemos encontrar en la situación que el proyecto sea normal, no requiera ninguna acción especial pero nos gustaría tener unos procesos definidos de como se deben hacer las cosas.

Bien, para cualquiera de las situaciones anteriores y seguramente alguna que otra mas, NodeJS nos ofrece un entorno y ciertas herramientas para crear nuestro propio CLI (Command-Line Interface).

¿Por donde comienzo?

Lo primero de todo sera crear la carpeta de nuestro CLI y dentro crearemos el fichero package.json.

Si no queremos complicarnos usaremos el comando npm init -y, el cual nos creara un package.json por defecto.

Lo siguiente sera instalar las dependencias necesarias para poder crear nuestro CLI:

chalk: Con esta dependencia podemos dar colores y ciertos estilos al texto de nuestro terminal (npm i chalk)

 

inquirer: Con esta dependencia podemos hacer que nuestra linea de comandos sea interactiva (npm i inquirer)

 

figlet: Con esta dependencia podemos crear banners con caracteres y simbolos (npm i figlet)

Con las dependencias instaladas, procederemos a crear el fichero que tendrá la lógica encargada de realizar la “magia”.

Empezando a crear nuestro CLI

Antes de comenzar a realizar nuestras pruebas, desde aquí podéis obtener el repositorio de Github.

Ahora ya podemos empezar, para este ejemplo lo llamaremos index.js

#!/usr/bin/env node
/* 
  La linea anterior es una instancia de una línea shebang: 
  la primera línea en un archivo ejecutable de texto sin formato en plataformas tipo Unix 
  que le dice al sistema a qué intérprete pasar ese archivo para su ejecución, 
  a través del comando línea siguiendo el prefijo máfico #! (llamado shebang).
  En Windows no admite líneas shebang, por lo que se ignoran allí; 
  en Windows, es únicamente la extensión del nombre de archivo de un archivo determinado 
  lo que determina qué ejecutable lo interpretará. 
  Sin embargo, aún los necesita en el contexto de npm.
*/
const chalk = require('chalk');
const figlet = require('figlet');
const inquirer = require('inquirer');
const fs = require('fs');
const pathBase = process.cwd();

// Template que usaremos para la creación del contenido del fichero
let templateVUE = require('./templates/templateVUE');

// Mostrar un banner con un mensaje formado por caracteres.
const msn = msn => {
  console.log(chalk.bold.cyan(figlet.textSync(msn, { 
    font:  'ANSI Shadow',
    horizontalLayout: 'default',
    verticalLayout: 'default'
  })));
}

// Preguntas que se van a realizar y que más tarde usaremos
const queryParams = () => {
  const qs = [{
      name: 'componentName',
      type: 'input',
      message: 'Escribe el nombre del componente'
    },{
      name: 'fileName',
      type: 'input',
      message: 'Escribe el nombre del fichero: '
    }, {
      name: 'type',
      type: 'list',
      message: 'Selecciona el tipo de elemento a crear: ',
      choices: [
        'Components',
        'Views',
        'Layouts',
        'Models',
        'Javascript',
      ],
    },
  ];

  return inquirer.prompt(qs);
};

// Método que se encarga de crear el fichero en base a las preguntas realizadas
const createFile = (data) => {
  const extension = data.type === 'Javascript' ? 'js' : 'vue'
  const path = `${pathBase}\\src\\${data.type}`;
  const file = `${path}\\${data.fileName}.${extension}`;
  if (!fs.existsSync(path)) {
    fs.mkdirSync(path, 0777);
  }
  try {
    templateVUE = templateVUE.replace('$name', data.componentName);
    fs.writeFileSync(file, templateVUE, { mode: 0o777 });
  } catch(err) {
    console.error(err);
  } finally {
    console.log(`
      ------ CREADO CORRECTAMENTE ------\n
      Se ha creado el siguiente elemento\n
      - Tipo: ${chalk.blue.bold(data.type)}\n
      - Ruta: ${chalk.blue.bold(file)}\n
      ----------------------------------\n
    `);
  }
}

// IIFE (Immediately Invoked Function Expression)
(async() => {
  msn('MTM-CLI');
  createFile(await queryParams());
})();

El fichero anterior es un ejemplo de lo que se puede hacer, realmente en el fichero index.js puedes desarrollar lo que necesites, desde crear archivos, tareas de backups, proyectos desde cero, etc…

En este punto nosotros podríamos ejecutar el fichero usando el siguiente comando: node index.js, y comenzaría a ejecutar el script:

Como podemos comprobar, hemos ejecutado nuestro archivo desde NodeJS y el código ha funcionado correctamente, dando como resultado la creación de un fichero dentro de una carpeta usando unos parámetros.

Pero claro, lo interesante es ejecutar el fichero sin ayuda del comando node xxxx, pues bien, para eso vamos a realizar los siguientes pasos.

Lo primero añadiremos, dentro del fichero package.json un nuevo valor llamado “bin”: “nombre de fichero”.

{
  "name": "mtm-cli",
  "version": "1.0.0",
  "description": "\"# create-cli\"",
  "main": "index.js",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/kimagure44/create-cli.git"
  },
  "keywords": [
    "cli",
    "javascript"
  ],
  "author": "Iván Lara Gómez, <ivan.lara.gomez.1977@gmail.com>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/kimagure44/create-cli/issues"
  },
  "homepage": "https://github.com/kimagure44/create-cli#readme",
  "dependencies": {
    "chalk": "^2.4.2",
    "figlet": "^1.2.4",
    "inquirer": "^7.0.0",
  },
  "bin": "./index.js"
}

Añadiendo esa linea, estaríamos definiendo el fichero que se va a ejecutar como si fuera un binario de forma global.

El nombre de nuestro CLI estará definido por la key name dentro del package.json, que para este ejemplo es “name”:”mtm-cli”.

El proceso estaría casi finalizado, solo necesitaríamos realizar un enlace simbólico a nuestra carpeta.

Para esa tarea usaremos el siguiente comando: npm link, que nos permite crear un enlace simbólico a nuestro paquete / carpeta.

Con el comando anterior estamos creando un enlace simbólico en la carpeta global ({prefix}/lib/node_modules/<package>) que enlaza con el paquete donde se ejecuto.

Recordar nuevamente, que el nombre que usaremos para ejecutar el CLI, viene definido por el key name dentro del package.json

Ahora ya podemos ejecutar nuestro CLI usando el nombre dentro del package.json, tanto el nuestra carpeta actual como en cualquier otra parte ya que el comando npm link nos creo un enlace simbólico de forma global.

Con este último paso ya tendríamos preparado nuestro CLI, para poder usarlo donde lo necesitemos.