Cómo crear aplicaciones de línea de comandos con Node.js

Índice
  1. Prerrequisitos
  2. Paso 1 – Entender el asunto
  3. Creando la aplicación Frase del día
  4. Creando una lista de tareas pendientes
  5. Conclusión

Como desarrollador, es probable que pases la mayor parte de tu tiempo en tu terminal, escribiendo comandos que te ayuden a realizar algunas tareas.

Algunos de estos comandos vienen integrados en su sistema operativo, mientras que otros se instalan a través de algún asistente de terceros como npm o brew, o incluso descargando un binario y agregándolo a su $PATH.

Un buen ejemplo de aplicaciones de uso común incluye npm, eslint, typescript y generadores de proyectos, como Angular CLI, Vue CLI o Create React App.

En este tutorial crearás dos pequeñas aplicaciones CLI en Node.js:

  1. Herramienta de cita del día que recupera citas del día de https://quotes.rest/qod.
  2. Una aplicación de lista de tareas pendientes que utiliza JSON para guardar datos.

Prerrequisitos

Para completar este tutorial, necesitarás:

  • Un entorno de desarrollo local para Node.js. Siga Cómo instalar Node.js y crear un entorno de desarrollo local

Paso 1 – Entender el asunto

Cada vez que mires cualquier archivo de script, verás caracteres como estos al comienzo del archivo:

file.sh#!/usr/bin/env sh

O esto:

file.py#!/usr/bin/env python -c

Sirven como una forma para que el cargador de programas de su sistema operativo localice y utilice el intérprete correcto para analizar su archivo ejecutable. Sin embargo, esto solo funciona en sistemas Unix.

De Wikipedia:

En informática, un shebang es la secuencia de caracteres que consta de los caracteres signo de número y signo de exclamación (#!) al comienzo de un script.

NodeJS tiene sus propios caracteres shebang compatibles.

Crea un nuevo archivo en tu editor llamado logger.js:

  1. nano logger.js

Añade el siguiente código:

#!/usr/bin/env nodeconsole.log("I am a logger")

La primera línea le indica al cargador del programa que analice este archivo con NodeJS. La segunda línea imprime texto en la pantalla.

Puedes intentar ejecutar el archivo escribiendo esto en tu terminal. Recibirás un mensaje que indica que se ha denegado el permiso para la ejecución.

  1. ./logger
Outputzsh: permission denied: ./logger

Debes darle permisos de ejecución al archivo. Puedes hacerlo con

  1. chmod +x logger
  2. ./logger

Esta vez verás el resultado.

OutputI am a logger

Podrías haber ejecutado este programa con node logger, pero agregar el shebang y hacer que el programa sea ejecutable con su propio comando te permite evitar escribir nodepara ejecutarlo.

Creando la aplicación Frase del día

Vamos a crear un directorio y llamarlo qod. Y dentro, crear una instancia de una aplicación NodeJs.

  1. mkdir qod
  2. cd qod
  3. npm init -y

A continuación, sabemos que necesitamos realizar solicitudes al servidor de cotizaciones, por lo que podríamos usar bibliotecas existentes para hacer precisamente esto. Usaremos axios

npm install --save axios

También agregaremos una tiza, una biblioteca para ayudarnos a imprimir color en la terminal.

npm install --save chalk

Luego escribimos la lógica necesaria para recuperar estas citas.

Crea un nuevo archivo llamado qod:

  1. nano qod

Agregue el siguiente código al qodarchivo para especificar el shebang, cargar las bibliotecas y almacenar la URL de la API:

qod

#!/usr/bin/env nodeconst axios = require('axios');const chalk = require('chalk');const url = "https://quotes.rest/qod";

A continuación, agregue este código para realizar una GETsolicitud a la API:

[label qod]// make a get request to the urlaxios({  method: 'get',  url: url,  headers: { 'Accept': 'application/json' }, // this api needs this header set for the request}).then(res = {  const quote = res.data.contents.quotes[0].quote  const author = res.data.contents.quotes[0].author  const log = chalk.green(`${quote} - ${author}`) // we use chalk to set the color green on successful response  console.log(log)}).catch(err = {  const log = chalk.red(err) // we set the color red here for errors.  console.log(log)})

Guarde el archivo.

Cambie los permisos del archivo para que sea ejecutable:

  1. chmod +x qod

Luego ejecuta la aplicación:

./qod

Verás una cita:

OutputThe best way to not feel hopeless is to get up and do something. Don’t wait for good things to happen to you. If you go out and make some good things happen, you will fill the world with hope, you will fill yourself with hope. - Barack Obama

Este ejemplo muestra que puede utilizar bibliotecas externas en sus aplicaciones CLI.

Ahora vamos a crear un programa CLI que guarde datos.

Creando una lista de tareas pendientes

Esto será un poco más complejo, ya que implicará el almacenamiento y la recuperación de datos. Esto es lo que estamos tratando de lograr.

  1. Necesitamos tener un comando llamadotodo
  2. El comando aceptará cuatro argumentos. new, get, complete, y help.

Así que los comandos disponibles serán

./todo new // create a new todo./todo get // get a list of all your todos./todo complete // complete a todo item../todo help // print the help text

Cree un directorio llamado todoy cree una instancia de una aplicación Node.js:

  1. mkdir todo
  2. cd todo
  3. npm install -y

A continuación, vuelve a instalar la tiza para que puedas iniciar sesión con colores.

npm install --save chalk

Lo primero que vamos a hacer es asegurarnos de que tenemos estos comandos disponibles. Para que funcionen, usaremos el proceso/argv de NodeJs, que devuelve una matriz de cadenas con los argumentos de la línea de comandos. La process.argvpropiedad devuelve una matriz que contiene los argumentos de la línea de comandos que se pasaron cuando se inició el proceso de Node.js.

Crea el archivo todo:

  1. nano todo

Añade esto al archivo todo.

hacer

#!/usr/bin/env nodeconsole.log(process.argv)

Otorgue permisos de ejecución al archivo y luego ejecútelo con un nuevo comando.

  1. chmod +x ./todo
  2. ./todo new

Obtendrás este resultado:

Output[ '/Users/sammy/.nvm/versions/node/v8.11.2/bin/node',  '/Users/sammy/Dev/scotch/todo/todo',  'new' ]

Observe que las dos primeras cadenas de la matriz son el intérprete y la ruta completa del archivo al programa. El resto de la matriz contiene los argumentos pasados; en este caso, es new.

Para estar seguros, vamos a restringirlos, de modo que solo podamos aceptar el número correcto de argumentos, que es uno, y solo pueden ser new, gety complete.

Modifique el todoarchivo para que se parezca al siguiente:

hacer

#!/usr/bin/env nodeconst chalk = require('chalk')const args = process.argv// usage represents the help guideconst usage = function() {  const usageText = `  todo helps you manage you todo tasks.  usage:    todo command    commands can be:    new:      used to create a new todo    get:      used to retrieve your todos    complete: used to mark a todo as complete    help:     used to print the usage guide  `  console.log(usageText)}// used to log errors to the console in red colorfunction errorLog(error) {  const eLog = chalk.red(error)  console.log(eLog)}// we make sure the length of the arguments is exactly threeif (args.length  3) {  errorLog(`only one argument can be accepted`)  usage()}

Primero asignamos los argumentos de la línea de comando a una variable y luego verificamos en la parte inferior que la longitud no sea mayor a tres.

También hemos añadido una usagecadena que imprimirá lo que espera la aplicación de línea de comandos. Ejecute la aplicación con parámetros incorrectos como se muestra a continuación.

  1. ./todo new app
Outputonly one argument can be acceptedtodo helps you manage you todo tasks.usage:  todo command  commands can be:  new:      used to create a new todo  get:      used to retrieve your todos  complete: used to mark a todo as complete  help:     used to print the usage guide

Si lo ejecuta con un parámetro, no imprimirá nada, lo que significa que el código pasa.

A continuación, debemos asegurarnos de que solo se esperan los cuatro comandos y que todo lo demás se imprimirá como no válido.

Agregue una lista de los comandos en la parte superior del archivo:

hacer

const commands = ['new', 'get', 'complete', 'help']

Y luego verifique con el comando pasado después de haber verificado la longitud:

hacer

...if (commands.indexOf(args[2]) == -1) {  errorLog('invalid command passed')  usage()}

Ahora, si ejecutamos la aplicación con un comando no válido, obtenemos esto.

  1. ./todo ne
Outputinvalid command passed  todo helps you manage you todo tasks.  usage:    todo command    commands can be:    new:      used to create a new todo    get:      used to retrieve your todos    complete: used to mark a todo as complete    help:     used to print the usage guide

Ahora implementemos el helpcomando llamando a la usagefunción. Agreguemos esto al archivo de tareas pendientes:

hacer

//...switch(args[2]) {  case 'help':    usage()    break  case 'new':    break  case 'get':    break  case 'complete':    break  default:    errorLog('invalid command passed')    usage()}//...

Tenemos una switchdeclaración que llamará a funciones según el comando que se haya llamado. Si observas con atención, notarás que el helpcaso solo llama a la función de uso.

El newcomando creará un nuevo elemento de la lista de tareas pendientes y lo guardará en un archivo json. La biblioteca que usaremos es lowdb. Podríamos escribir fácilmente funciones para leer y escribir en un archivo json, si quisiéramos.

Instalar lowdb

  1. npm install --save lowdb

Agreguemos [readline](https://nodejs.org/api/readline.html)dependencias lowdbpara que nos ayuden a almacenar datos. El código de LowDB es estándar en su página de GitHub.

hacer

//...const rl = require('readline');const low = require('lowdb')const FileSync = require('lowdb/adapters/FileSync')const adapter = new FileSync('db.json')const db = low(adapter)// Set some defaults (required if your JSON file is empty)db.defaults({ todos: []}).write()//...

A continuación, agregaremos una función para solicitar al usuario que ingrese datos.

hacer

//...function prompt(question) {  const r = rl.createInterface({    input: process.stdin,    output: process.stdout,    terminal: false  });  return new Promise((resolve, error) = {    r.question(question, answer = {      r.close()      resolve(answer)    });  })}//...

Aquí usamos la biblioteca readline para crear una interfaz que nos ayudará a solicitarle al usuario que escriba y luego leer la salida.

A continuación, debemos agregar una función que se llamará cuando un usuario escriba el newcomando:

hacer

//...function newTodo() {  const q = chalk.blue('Type in your todon')  prompt(q).then(todo = {    console.log(todo)  })}//...

Usaremos tiza para obtener el color azul de la indicación y luego registraremos el resultado.

Por último, llama a la función en el newcaso.

hacer

// ...switch(args[2]) {  //...  case 'new':    newTodo()    break// ...}// ...

Cuando ejecutes la aplicación ahora con el nuevo comando, se te solicitará que agregues una tarea pendiente. Escribe y presiona Enter.

  1. ./todo new
OutputType in your todoThis my todo  aaaaaaw yeahThis my todo  aaaaaaw yeah

Deberías ver algo similar a esto.

Tenga en cuenta también que db.jsonse ha creado un archivo en su sistema de archivos y que tiene una propiedad todos.

A continuación, agreguemos la lógica para agregar una tarea pendiente. Modifique la función newTodo.

hacer

//...function newTodo() {  const q = chalk.blue('Type in your todon')  prompt(q).then(todo = {    // add todo    db.get('todos')      .push({  title: todo,  complete: false  })      .write()  })}//...

Ejecute el código nuevamente.

  1. ./todo new
OutputType in your todoTake a Scotch course

Si miras tu db.json, verás que se agregó la tarea pendiente. Agrega dos más, para que podamos recuperarlas en el próximo comando get. Así db.jsonse ve el archivo con más registros:

base de datos.json

{  "todos": [    {      "title": "Take a Scotch course",      "complete": false    },    {      "title": "Travel the world",      "complete": false    },    {      "title": "Rewatch Avengers",      "complete": false    }  ]}

Después de crear el newcomando, ya debería tener una idea de cómo implementarlo get.

Crea una función que recuperará los todos.

hacer

//...function getTodos() {  const todos = db.get('todos').value()  let index = 1;  todos.forEach(todo = {    const todoText = `${index++}. ${todo.title}`    console.log(todoText)  })}//...// switch statementsswitch(args[2]) {//...case 'get':getTodos()break//...}//....

Ejecute el comando nuevamente:

  1. ./todo get

Al ejecutar la aplicación ahora se obtendrá este resultado:

Output1. Take a Scotch course2. Travel the world3. Rewatch Avengers

Puedes hacer que el color sea verde usando chalk.green.

A continuación, agrega el completecomando, que es un poco complicado.

Puedes hacerlo de dos maneras.

  1. Cada vez que un usuario escribe ./todo complete, podemos enumerar todas las tareas pendientes y luego pedirle que escriba el número o la tecla que desea marcar como completada.
  2. Podemos agregar otro parámetro, de modo que un usuario pueda escribir ./todo gety luego elegir la tarea que desea marcar como completada con un parámetro, como por ejemplo ./todo complete 1.

Como aprendiste cómo hacer el primer método cuando implementaste el newcomando, veremos la opción 2.

Con esta opción, el comando ./todo complete 1, no superará nuestra comprobación de validez para la cantidad de comandos proporcionados. Por lo tanto, primero debemos solucionar este problema. Cambie la función que comprueba la longitud de los argumentos por esta:

hacer

//...// we make sure the length of the arguments is exactly threeif (args.length  3  args[2] != 'complete') {  errorLog('only one argument can be accepted')  usage()  return}///...

Este enfoque utiliza tablas de verdad, donde TRUE FALSEserá igual a FALSEy se omitirá el código cuando completese pase.

Luego tomaremos el valor del nuevo argumento y haremos que el valor de todo sea completado:

hacer

//...function completeTodo() {  // check that length  if (args.length != 4) {    errorLog("invalid number of arguments passed for complete command")    return  }  let n = Number(args[3])  // check if the value is a number  if (isNaN(n)) {    errorLog("please provide a valid number for complete command")    return  }  // check if correct length of values has been passed  let todosLength = db.get('todos').value().length  if (n  todosLength) {    errorLog("invalid number passed for complete command.")    return  }  // update the todo item marked as complete  db.set(`todos[${n-1}].complete`, true).write()}//...

Además, actualice la switchdeclaración para incluir el completecomando:

hacer

//...case 'complete':    completeTodo()    break//...

Cuando ejecutes esto con ./todo complete 2, notarás que tu db.jsonha cambiado a esto, marcando la segunda tarea como completada:

base de datos.json

{  "todos": [    {      "title": "Take a Scotch course",      "complete": false    },    {      "title": "Travel the world",      "complete": true    },    {      "title": "Rewatch Avengers",      "complete": false    }  ]}

Lo último que tenemos que hacer es cambiar ./todo getpara que solo se muestren las tareas que ya se han realizado. Para ello, utilizaremos emojis. Modifique getTodoscon este código:

hacer

//...function getTodos() {  const todos = db.get('todos').value()  let index = 1;  todos.forEach(todo = {    let todoText = `${index++}. ${todo.title}`    if (todo.complete) {      todoText += ' ✔ ️' // add a check mark    }    console.log(chalk.strikethrough(todoText))  })  return}//...

Cuando escribas ahora ./todo getverás esto.

Output1. Take a Scotch course2. Travel the world ✔ ️3. Rewatch Avengers

Conclusión

Ha escrito dos aplicaciones CLI en Node.js.

Una vez que la aplicación esté funcionando, coloque el archivo en una bincarpeta. De esta manera, npm sabrá cómo trabajar con el ejecutable cuando lo distribuya. Además, independientemente de dónde coloque el ejecutable, debe actualizar package.jsonla propiedad bin.

El objetivo de este artículo fue analizar cómo se crean las aplicaciones CLI con nodejs vanilla, pero cuando se trabaja en el mundo real, sería más productivo utilizar bibliotecas.

Aquí tienes una lista de bibliotecas útiles para ayudarte a escribir increíbles aplicaciones CLI, que puedes publicar en npm.

  1. vopral: marco de trabajo CLI interactivo con todas las funciones
  2. Meow – Biblioteca auxiliar de CLI
  3. commanderjs – biblioteca de línea de comandos
  4. minimista – para análisis de argumentos
  5. yargs – análisis de argumentos

Y ni hablar de las bibliotecas como Chalk que nos ayudaron con los colores.

Como ejercicio adicional, intente agregar un Deletecomando a la CLI.

SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion