DEV Community

Heiker
Heiker

Posted on Edited on Originally published at vonheikemen.github.io

Extendiendo deno cli usando una funciÃģn

Estos días he estado usando deno con mÃĄs frecuencia y debo decirles que aÚn hay un par de cosas que me molestan. Sí logrÃĐ resolver algunos de esos problemas "agregando" algunos sub-comandos. No, no es magia negra, sÃģlo un pequeÃąo truco que aprendí hace tiempo y hoy voy a decirles cÃģmo funciona.

ÂŋQuÃĐ necesitamos?

En teoría cualquier intÃĐrprete que nos permita crear funciones debería ser suficiente. En este artículo les estarÃĐ mostrando los ejemplos usando una sintaxis compatibles con intÃĐrpretes que obedecen el estÃĄndar POSIX (piensen en "shells" como zsh, bash, ash, etc...).

ÂŋQuÃĐ queremos lograr?

Personalmente a mí me encantaría que deno tuviera algÚn equivalente nativo a los npm scripts, pero parece que no tendremos nada de eso aÚn. Quisiera poder hacer algo así.

deno start hello world
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

TambiÃĐn me gustaría poder inicializar esos scripts, porque deben existir en algÚn lado.

deno init
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Como ya deben saber los sub-comandos start e init no existen y vamos a arreglar eso (casi).

Empecemos

El primer paso sería ubicar el archivo que su intÃĐrprete favorito ejecuta cada vez que lo invocan. En zsh por ejemplo, pueden usar ~/.zshrc. En bash pueden usar ~/.bashrc. Esos dos son los Únicos que conozco, si usan otro intÃĐrprete busquen en su documentaciÃģn algÚn equivalente.

La DefiniciÃģn

ÂŋEstÃĄn listos? Ahora deben crear una funciÃģn en ese archivo.

deno()
{
  echo "Hola"
}
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Ahora si reinician el intÃĐrprete (o "recargan" su configuraciÃģn) e invocan el comando deno deberían obtener Hola como resultado. Genial, hemos creado un problema, no podemos usar el comando deno. No teman, vamos por buen camino.

Vuelve a mi, deno

Si quisieramos invocar el comando deno lo Único que tenemos que hacer es usar el comando command así:

command deno --version
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Eso debería mostrarle la informaciÃģn de deno.

deno x.x.x (release, x86_64-unknown-linux-gnu)
v8 x
typescript x.x
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Con este nuevo conocimiento podemos resolver nuestro problema. Vamos a hacer una prueba.

deno()
{
  echo "EstÃĄs usando"
  command deno --version
}
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Y esto es lo que debería ocurrir al invocarlo.

$ deno

EstÃĄs usando
deno x.x.x (release, x86_64-unknown-linux-gnu)
v8 x
typescript x.x
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Volviendo a la normalidad

Ahora que sabemos cÃģmo llamar a deno lo que deberíamos hacer ahora es imitar el comportamiento de deno en nuestra funciÃģn. Debemos hacer esto de una manera que nos deje espacio para agregar nuestros propios comandos, para esto vamos a usar la declaraciÃģn case.

deno()
{
  local cmd=$1; shift;

  case "$cmd" in
    *)
      command deno $cmd $@
    ;;
  esac
}
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

La primera línea lo que hace es asignar el primer parÃĄmetro ($1) a la variable cmd y luego lo elimina de la lista de argumentos ($@). DespuÃĐs lo que hacemos es comparar cmd con un patrÃģn. Por ahora el Único patrÃģn que tenemos es *, que actÚa como un comodín que coincide con todo, bÃĄsicamente es nuestro default. Probemos.

deno --version
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Bien, pero ahora si intentamos usar deno sin argumentos nos darÃĄ un error. Para arreglar eso vamos verificar si el primer parÃĄmetro estÃĄ vacío, si ese es el caso sÃģlo llamamos deno.

deno()
{
  if [ -z "$1" ];then
    command deno
    return
  fi

  local cmd=$1; shift;

  case "$cmd" in
    *)
      command deno $cmd $@
    ;;
  esac
}
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Invocando sub-comandos

Ya estamos en el lugar que queremos, finalmente podremos agregar nuestros propios sub-comandos. Veamos cÃģmo funcionaría.

  deno()
  {
    if [ -z "$1" ];then
      command deno
      return
    fi

    local cmd=$1; shift;

    case "$cmd" in
+     hola)
+       command deno eval "console.log('deno dice hola')"
+     ;;
      *)
        command deno $cmd $@
      ;;
    esac
  }
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Si invocamos el comando hola:

$ deno hola

deno dice hola
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Ahora estamos seguros que funciona.

deno scripts

Ya podemos comenzar con lo mÃĄs importante, nuestro reemplazo para los npm scripts. Lo que quiero hacer primero es crear el comando start, el cual es el mÃĄs comÚn que usaría.

  deno()
  {
    if [ -z "$1" ];then
      command deno
      return
    fi

    local cmd=$1; shift;

    case "$cmd" in
+     start)
+       command deno run --allow-run ./Taskfile.js start $@
+     ;;
      *)
        command deno $cmd $@
      ;;
    esac
  }
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Esto lo que harÃĄ serÃĄ ejecutar un archivo llamado Taskfile.js usando deno. --allow-run nos permitirÃĄ usar Deno.run en nuestro cÃģdigo para poder llamar comandos externos dentro de Taskfile.js. Ahora vamos a usarlo.

Creamos Taskfile.js.

const cmd = ['echo', 'Taskfile: ', ...Deno.args];
Deno.run({ cmd });
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Y llamamos start de esta manera.

$ deno start hola

Taskfile start hola
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Genial. El siguiente paso sería crear un Taskfile.js "mÃĄs inteligente," que sea capaz de ejecutar varias tareas. Ya he hecho algo así en el pasado (pueden encontrar los detalles aquí), así que les mostrarÃĐ el cÃģdigo que yo usaría.

const entrypoint = "./src/main.js";

run(Deno.args, {
  start(...args) {
    exec(["deno", "run", entrypoint, ...args]);
  },
  list() {
    console.log('Available tasks: ');
    Object.keys(this).forEach((k) => console.log(`* ${k}`));
  },
});

function run([name, ...args], tasks) {
  if(tasks[name]) {
    tasks[name](...args);
  } else {
    console.log(`Task "${name}" not found\n`);
    tasks.list();
  }
}

async function exec(args) {
  const proc = await Deno.run({ cmd: args }).status();

  if (proc.success == false) {
    Deno.exit(proc.code);
  }

  return proc;
}
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Con esto podríamos ejecutar el archivo ./src/main.js al invocar deno start. Ahora tenemos otro problema, no queremos escribir todo eso cada vez que queremos iniciar un proyecto. Lo que haremos serÃĄ crear un comando init que copie esta plantilla en la carpeta de nuestro proyecto.

  deno()
  {
    if [ -z "$1" ];then
      command deno
      return
    fi

    local cmd=$1; shift;

    case "$cmd" in
      start)
        command deno run --allow-run ./Taskfile.js start $@
      ;;
+     init)
+       cp /path/to/template/Taskfile.js ./
+       echo "Taskfile.js created"
+     ;;
      *)
        command deno $cmd $@
      ;;
    esac
  }
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Ya tiene buena forma. La Última cosa que nos quedaría por hacer es manejar otras tareas ademÃĄs de start. Con npm podemos invocar npm run para elegir quÃĐ script queremos ejecutar, nuestro equivalente se llamarÃĄ x.

  deno()
  {
    if [ -z "$1" ];then
      command deno
      return
    fi

    local cmd=$1; shift;

    case "$cmd" in
+     x)
+       command deno run --allow-run ./Taskfile.js $@
+     ;;
      start)
        command deno run --allow-run ./Taskfile.js start $@
      ;;
      init)
        cp /path/to/template/Taskfile.js ./
        echo "Taskfile.js created"
      ;;
      *)
        command deno $cmd $@
      ;;
    esac
  }
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Ahora podríamos tener una "tarea" llamada test:api y llamarla de esta manera.

deno x test:api
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Un poco mÃĄs de conveniencia

Lo Último que quiero hacer es crear una forma de ejecutar un script el cual pueda llamar las librerías que uso con mÃĄs frecuencia sin usar su URL. Podemos hacer esto con el apoyo de un import-map, un archivo .json en el cual podemos vincular un "alias" a una URL.

Yo uso uno como este.

{
  "imports": {
    "@std/": "https://deno.land/std@0.93.0/",
    "@npm/": "https://jspm.dev/",

    "ansi-colors": "https://jspm.dev/ansi-colors@4.1.1",
    "arg": "https://jspm.dev/arg@5.0.0",
    "cheerio": "https://jspm.dev/cheerio@1.0.0-rc.5",
    "exec": "https://deno.land/x/exec@0.0.5/mod.ts",
    "ramda": "https://jspm.dev/ramda@0.27.1",

    "@utils/": "/path/to/deno/utils/"
  }
}
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

deno puede leerlo si le pasamos el argumento --import-map. Ahora vamos a crear un comando para usarlo.

  deno()
  {
    if [ -z "$1" ];then
      command deno
      return
    fi

    local cmd=$1; shift;

    case "$cmd" in
      x)
        command deno run --allow-run ./Taskfile.js $@
      ;;
      start)
        command deno run --allow-run ./Taskfile.js start $@
      ;;
      init)
        cp /path/to/template/Taskfile.js ./
        echo "Taskfile.js created"
      ;;
+     s|script)
+       command deno run --import-map="/path/to/deno/import-map.json" $@
+     ;;
      *)
        command deno $cmd $@
      ;;
    esac
  }
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Como ya es costumbre, tenemos que probar esto. Vamos a crear un script de prueba con este contenido.

import dayjs from '@npm/dayjs';
import c from 'ansi-colors';

c.enabled = !Deno.noColor;

const date = dayjs().format('{YYYY} MM-DDTHH:mm:ss SSS [Z] A');

console.log(c.green(date));
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

Tal vez quieran especificar la versiÃģn de la librería que estÃĄn usando con @npm, en ese caso harían esto @npm/dayjs@1.10.4

Lo ejecutamos.

$ deno script ./test.js

Download https://jspm.dev/dayjs@1.10.4
Download https://jspm.dev/npm:dayjs@1.10.4!cjs
{2021} 04-18T11:28:05 929 Z AM
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

ÂĄJaja! Ahora tengo todo lo que quiero.

El resultado final

DespuÃĐs de todo este proceso la funciÃģn deno debería quedar así.

deno()
{
  if [ -z "$1" ];then
    command deno
    return
  fi

  local cmd=$1; shift

  case "$cmd" in
    x)
      command deno run --allow-run ./Taskfile.js $@
    ;;
    start)
      command deno run --allow-run ./Taskfile.js start $@
    ;;
    init)
      cp /path/to/template/Taskfile.js ./
      echo "Taskfile.js created"
    ;;
    s|script)
      command deno run --import-map="/path/to/deno/import-map.json" $@
    ;;
    *)
      command deno $cmd $@
    ;;
  esac
}
ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™ ðŸ”ī Hot News: vonheikemen/extendiendo deno cli usando una funcion - āļ›āļĢāļ°āđ€āļ”āđ‡āļ™āļĢāđ‰āļ­āļ™

ConclusiÃģn

Entonces, ÂŋquÃĐ hicimos hoy? "Escondimos" el comando deno detrÃĄs de una funciÃģn para poder agregar "sub-comandos" creados por nosotros. Logramos crear una especie de equivalente de npm run para deno y finalmente usamos import maps para simplificar la declaraciÃģn de dependencias en un script. No estÃĄ mal para un día de trabajo.


Gracias por su tiempo. Si este artículo les pareciÃģ Útil y quieren apoyar mis esfuerzos para crear mÃĄs contenido pueden dejar una propina en ko-fi.com/vonheikemen.

buy me a coffee

Top comments (0)