5 consejos para crear hermosas CLI con Node.js, Yargs y Ink

Cuando lanzamos por primera vez un producto que podía construir un dapp a partir de un ABI, el sueño era integrar esa funcionalidad en el centro del flujo de trabajo del desarrollador de : sus proyectos Truffle.

La suite Truffle de herramientas para desarrolladores es una parte esencial de la creación de prototipos de contratos inteligentes. Si íbamos a acortar con éxito el ciclo de desarrollo, los usuarios no podrían copiar y pegar ABI de los archivos de compilación a los navegadores. Con nuestro nuevo comando dappbot en npm, puede crear una interfaz alojada y escalable en menos de 5 minutos para cualquier contrato compilado e implementado a través de Truffle.

Como usuario de Truffle, ahora está a solo tres comandos de crear un dapp. Ejecute los siguientes comandos desde la raíz de su proyecto:

npm install @ eximchain / dappbot-cli -g: instale la CLI, que le mostrará automáticamente algunos comandos de muestra. Registro de Adaptbot: cree una cuenta de forma interactiva, verifique su correo electrónico e inicie sesión por primera vez; su inicio de sesión se almacena en un dappbotAuthData.json file.dappbot truffle: lo guía a través de la elección de:
1. ¿Para cuáles de sus contratos inteligentes le gustaría hacer un dapp?
2. ¿Con cuál de las redes implementadas del contrato debe hablar el dapp?
3. ¿Cómo llamas a tu dapp? (por ejemplo, hub.dapp.bot/my-new-dapp).

La CLI también admite todos los métodos de impago de la API bajo el subcomando dappbot api. Por ejemplo, puede inspeccionar su conjunto actual de dapps llamando a dappbot api private / listDapps:

Escribimos @ eximchain / dappbot-cli en Typecript, sobre cuatro dependencias clave:

hilados es "una biblioteca de node.js para hearties tryin ’ter pats optstrings". Sale de la caja con bastante salida de ayuda, fuerte soporte de subcomandos y un sistema de middleware para procesar previamente los argumentos antes de que lleguen al controlador. Más sobre eso más tarde.tinta es "Reaccionar para CLIs". Cree y pruebe su salida de CLI usando componentes ". Cuando ejecuta una instalación npm y ve una salida dinámica, la mayoría desaparece cuando se completa el comando, ve tinta en acción. Si ha realizado cualquier cantidad de mezcla de cadenas para hacer CLI bonitas, verá por qué la tinta es una bendición absoluta.@ eximchain / dappbot-types es nuestra biblioteca de mecanografiado de código abierto que recopila todos los tipos de claves del sistema DappBot. Viene con protectores de tipo para validar formas de objetos, funciones de fábrica para producir objetos de muestra y constantes que describen cada parte de la API.@ eximchain / dappbot-api-client es nuestra biblioteca de nodos de código abierto para interactuar con la API DappBot. Solo necesita un lugar para almacenar los datos de autenticación y luego puede realizar todas las solicitudes a la API DappBot. Aprovecha nuestro paquete de tipos para que cada función esté marcada.

Nuestro uso anterior con una CLI de yargs era configurar uno o dos comandos directamente en su exportación principal, pero para las CLI más involucradas como esta, yargs tiene .commandDir (). En lugar de alinear completamente cada comando como este:

En su lugar, puede crear un conjunto de módulos anidados, donde cada módulo exporta una clave correspondiente a los valores anteriores:

La estructura de este módulo facilita el diseño de subcomandos anidados y mantiene cada archivo ordenado. Nuestra estructura general de archivos se parece al árbol de abajo, donde cada archivo dentro de src / rootCmds tiene exportaciones como el ejemplo anterior:

En el inicio, src / cli.tsx llama a yargs.commandDir ('./ rootCmds') para cargar cada uno de los comandos de nivel superior. Cuando se carga src / rootCmds / api.tsx, llama a yargs.commandDir () en cada uno de los directorios authCmds, privateCmds y publicCmds, montando todos sus comandos debajo de la aplicación dappbot.

Todo el código React real vive dentro de src / ui, distinto del código de configuración de yargs. Cada módulo de comando importa la interfaz que necesita, la configura y luego llama al renderizado de ink en el controlador. Por ejemplo, a continuación se muestra la función del controlador para el comando más simple, dappbot goto . Muestra un mensaje de éxito, luego usa open () para abrir la URL correcta en el navegador predeterminado del usuario.

Todas y cada una de las funciones del controlador tienen una llamada de representación como la de la línea 5.

Este diseño facilitó la separación de las preocupaciones sobre la configuración de comandos individuales de los resultados renderizados, lo que permite una mayor reutilización del código entre los comandos.

Sin más preámbulos, aquí hay 5 cosas que aprendimos en el camino para garantizar que esta CLI tenga una experiencia de usuario limpia:

En nuestras CLI de nodo anteriores, como abi2api, el nombre del comando coincide con el nombre del paquete. Cuando ese fue el caso, declaramos el paquete como un comando binario al configurar la clave "bin" de package.json en la ruta del archivo de índice integrado, como se muestra a continuación, lo que hizo que el nombre del comando sea el predeterminado para el paquete.

// package.json
{
"nombre": "abi2api",
"bin": "build / index.js",

}

En este nuevo proyecto, no queríamos que la CLI tomara el nombre del paquete @ eximchain / dappbot, pero también queríamos que el comando fuera lo más sencillo posible. Observamos cómo Typecript lo hace, ya que su nombre de comando es tsc, y descubrimos la sintaxis de npm para declarar múltiples comandos:

// package.json
{
"nombre": "@ eximchain / dappbot-cli",
"bin": {
"dappbot": "build / cli.js"
},

}

Esta sintaxis de package.json le permite declarar múltiples comandos que apuntan a diferentes archivos JS, junto con la personalización de todos sus nombres. ¡Cuanto más sepas!

Queremos que nuestros usuarios puedan ponerse en marcha sin salir de la línea de comandos; eso significa que el "proceso de incorporación" tradicional debe ser activado por el usuario que instala el paquete. Nuestra solución fue enviar un mensaje de incorporación personalizado después de que el usuario instala el paquete, mostrando los comandos clave que necesita para comenzar con éxito. Utilizamos dos características diferentes para lograr esto:

correr npm i -g @ eximchain / dappbot-cli y verá nuestra información de incorporación de CLI.

Primero, agregamos un comando dappbot onboarding que generaría este mensaje personalizado. Esto nos permite crear un mensaje personalizado que es más simple que el texto de ayuda del comando completo. Además, ¡podríamos agregar texto divertido a través de ink-big-text! Sin embargo, no queríamos que este comando apareciera en los mensajes de ayuda. Afortunadamente, yargs tiene una forma idiomática de ocultar comandos:

Al proporcionar false en lugar de una cadena, los hilos saben que no deben mostrar este comando en el mensaje de ayuda.

Finalmente, activamos este nuevo comando a través del script postinstall en package.json.

// package.json
{

"guiones": {
"postinstall": "nodo build / cli.js onboarding",

}
}

npm es compatible con los scripts anteriores y posteriores para cualquier comando incorporado o personalizado. Si configuramos nuestro script postinstall para la incorporación del nodo build / cli.js, podemos activar automáticamente el mensaje para los nuevos usuarios. Tenga en cuenta que el archivo de compilación se llama directamente; esto se comporta correctamente en el desarrollo, mientras que la incorporación de dappbot solo funcionaría cuando el paquete esté realmente instalado globalmente.

La CLI acepta algunas opciones que representan rutas a los archivos, como cuando realiza manualmente la llamada API para actualizar un Dapp:

$ dappbot api private / updateDapp –abiPath ./path/to/Abi.json

Este problema surgió varias veces, por lo que utilizamos middleware y una convención de nomenclatura para resolverlo genéricamente para todos los comandos. La siguiente función verifica todas las opciones pasadas a cada comando, y si su nombre termina en "Ruta", carga el contenido del archivo allí como una cadena, arrojando un error si no se encuentra ninguno.

Esta solución fue para el desarrollo ergonómico, ya que el comando pudo configurar simplemente una opción de AbiPath para un nombre de archivo, luego el controlador pudo verificar su contenido en AbiFile:

Una de las grandes diferencias sobre el uso de React en una aplicación de línea de comandos es la frecuencia con la que llama a la función render (). En la mayoría de las aplicaciones React del navegador, render () se llama una vez dentro de un archivo root index.js o una etiqueta de script en index.html. Nuestra CLI, sin embargo, esencialmente tiene un "punto de montaje" separado para cada subcomando.

Yargs se siente como escribir un enrutador Express, donde cada controlador es responsable de devolver su propia respuesta. El sistema de middleware también me recordó a Express. La desventaja de esto es que cada comando necesita representar toda la interfaz de usuario, incluidos todos los contenedores necesarios y la lógica de configuración.

En nuestro caso, esto significa inicializar el cliente API con un mecanismo para almacenar datos de autenticación (como el configurador de useState () de React). El patrón renderProp funcionó bien aquí, ya que el componente de la aplicación de nivel superior configuró toda la plantilla, luego el comando de representación podría usar esa plantilla en la función de representación. Esto hizo algunas funciones cortas del controlador api, como esta para dappbot api public / viewDapp:

La instancia de API proporcionada como argumento para renderFunc está totalmente configurada y tiene los datos de autenticación almacenados del usuario, si hay alguno presente. La implementación de la API crea API y resuelve de manera invisible un segundo problema: incluir el . Los comentarios sobre esta versión simplificada del componente recorren la configuración clave repetitiva:

Las piezas más importantes a tener en cuenta son:

línea 11, donde usamos args.authFile para inicializar authData stateline 30, donde configuramos react-request-hook's línea 34, donde llamamos renderFunc junto con la API recién configurada.

Por último, pero no menos importante, el problema más espinoso que resolvimos al construir este cliente CLI fue administrar la autenticación y las sesiones persistentes. Nuestra solución tiene algunas piezas clave que trabajan juntas para proporcionar una experiencia de desarrollo perfecta.

Datos de autenticación persistentes con "fs"

Primero, necesitábamos una forma de persistir convenientemente la autenticación en los recorridos. Nuestra solución ingenua era simplemente aceptar una ruta a un archivo authData, pero no fue genial hacer que el usuario administrara la ubicación de su autenticación. En cambio, nos instalamos en el pequeño servicio authStorage.ts a continuación.

Observe cómo en la línea 4, formamos la ruta del archivo en función de la variable __dirname que corresponde al directorio de este archivo (dappbot-cli / build / services).

Cuando npm instala –global, el paquete se guarda en un nodo_módulos compartido ubicado junto a su nodo binario. La ubicación exacta de esto varía según su configuración (por ejemplo, usando un administrador de versión de nodo), pero es una ubicación estable donde siempre podemos verificar el archivo del usuario actual. Esta función saveAuthToFile se usa alrededor de la CLI, donde queramos conservar los authData que acabamos de recibir del servidor.

Cargando y requiriendo autenticación con Middlewares

Luego tenemos dos middlewares clave para administrar la autenticación. Primero, addDefaultAuthPath busca una opción de authPath proporcionada manualmente y luego agrega AUTH_FILE_PATH si no existe ninguna. Montamos esto en el comando de nivel superior, asegurando que cada subcomando tenga acceso a un archivo auth:

A continuación, queríamos restringir fácilmente algunos subcomandos para que solo se invoquen cuando el usuario haya iniciado sesión. En lugar de recibir un error no autorizado, es más claro obtener un error que le permita al usuario saber lo que debe hacer. Esta versión muy comentada del middleware requireAuthData () muestra cómo validamos el archivo authFile y rescatamos el proceso si es necesario.

Todos estos validadores provienen de nuestro paquete de tipos compartidos; estamos realizando exactamente las mismas comprobaciones que suceden en nuestro cliente web React. El middleware requireAuthData () se monta por comando, lo que nos permite decidir por separado cuáles deben requerir un inicio de sesión activo:

los constructor función exportada por `src / rootCmds / privateCmds / listDapps.tsx`.

Auto-Refresh Auth con React Hooks y renderProps

Finalmente, completamos el ciclo actualizando automáticamente authData obsoleto. El ciclo de actualización es una de esas cosas frustrantes que los buenos conjuntos de herramientas mantienen en segundo plano. Los usuarios finales no quieren pensar en ello, y tampoco los desarrolladores crean nuevos subcomandos. Pudimos habilitar esta experiencia limpia de usuario y desarrollador al realizar tres actualizaciones clave para componente contenedor Pasemos por ellos:

En las líneas 7–10, actualizamos la API setAuthData () para que también persista nuevos authData en el sistema de archivos, garantizando que cuando se llama, el nuevo valor persiste en la próxima ejecución. En las líneas 14–18, agregamos un efecto que comprueba si la API está obsoleta y, si es necesario, llama a su método auxiliar API.refreshAuth (). Esto realizará internamente la solicitud de inicio de sesión y llamará a la función setAuthData () proporcionada, actualizando el archivo. En las líneas 20–22, realizamos espere hasta que authData ya no esté obsoleto antes de llamar a renderFunc. Si tenemos que esperar, le mostramos al usuario un cargador. Esto significa que renderFunc nunca tendrá un solo render con authData obsoleto, lo que simplifica nuestro modelo mental al desarrollar subcomandos individuales

El middlewares, el enlace y el sistema de archivos trabajan juntos para crear una experiencia de usuario y desarrollo sin interrupciones. Aquí está el controlador para dappbot api private / listDapps, sin tener que preocuparse por la autenticación:

y aquí está el UX limpio del GIF al comienzo de la publicación:

Esperamos que esta publicación de blog arroje algo de luz para los futuros desarrolladores que están creando sus propias CLI de node.js. La decisión ha valido la pena para nosotros, lo que hace que sea mucho más fácil probar e interactuar con DappBot. Si sigue este camino y encuentra que uno de estos consejos lo ayuda, infórmenos en los comentarios.

Happy Hacking!

Eximchain permite a las empresas conectarse, realizar transacciones y compartir información de manera más eficiente y segura a través de nuestra infraestructura de utilidad . Usando la tecnología de Eximchain, las empresas pueden eliminar las barreras tradicionales de la cadena de suministro e integran actores grandes y pequeños en una red global eficiente, transparente y segura.

Ver GitHub de Eximchain

Síguenos en Twitter