Scripts sin shell con Execa 7

Mar 12 2023
Si bien los scripts de shell son muy útiles, muchos desarrolladores disfrutan de la flexibilidad, la expresividad y el ecosistema que vienen con lenguajes de programación como JavaScript. zx, un proyecto popular reciente de Google, combina lo mejor de ambos mundos.
Playa casi libre de conchas (foto de Aaron Burden)

Si bien los scripts de shell son muy útiles, muchos desarrolladores disfrutan de la flexibilidad, la expresividad y el ecosistema que vienen con lenguajes de programación como JavaScript. zx, un proyecto popular reciente de Google, combina lo mejor de ambos mundos.

const branch = await $`git branch --show-current`
await $`deploy --branch=${branch}`

import { $ } from 'execa'

const branch = await $`git branch --show-current`
await $`deploy --branch=${branch}`

¿Por qué usar tanto Node como Bash? Con Execa, no hay que recordar la sintaxis de shell: todo es simplemente JavaScript. Casi todas las funciones específicas de shell se pueden expresar en JavaScript. Para los casos extremos restantes, hay una shellopción disponible.

Esto es más:

  • Seguro : las inyecciones de caparazón no son técnicamente factibles.
  • Multiplataforma : no todas las máquinas Windows tienen Bash disponible.
  • Rendimiento : generar un caparazón para cada comando tiene un costo. Incluso npxse puede omitir ya que Execa puede ejecutar directamente binarios instalados localmente.
  • # In Bash
    API_KEY="secret" npx deploy "$cluster" &> logs.txt && echo "done"
    // In JavaScript
    import { $ } from 'execa'
    
    const options = { env: { API_KEY: 'secret' } }
    await $(options)`deploy ${cluster}`.pipeAll(’logs.txt’)
    console.log('done')
    

import { $ } from 'execa'
import pRetry from 'p-retry'

await pRetry(
  async () => await $`deploy dev_cluster`,
  { retries: 5 },
)

El child_processmódulo principal de Node incluye muchas funciones útiles: tiempo de espera, cancelación, procesos en segundo plano , IPC , PID , UID/GID, referencias débiles y más. Execa agrega algunos adicionales : terminación elegante , limpieza , salida intercalada , etc. Estos se pueden configurar usando $(options)uno o varios comandos.

import { $ } from 'execa'

const $$ = $({ timeout: 5000, all: true })

// `all` retrieves both stdout and stderr
const { all } = $$`deploy dev_cluster`

Los procesos secundarios pueden ser difíciles de depurar. Por eso Execa:

  • Incluye una verboseopción , que se puede configurar usando NODE_DEBUG=execa.
  • Informa de mensajes de error y propiedades detallados .
  • Es puramente sin estado, por lo que es sencillo averiguar cuál es el directorio actual o cualquier otra opción.
  • > NODE_DEBUG=execa node deploy.js
    
    [16:50:03.305] deploy dev_cluster
    ........dev_cluster successfully deployed.
    
    [16:53:06.378] deploy prod_cluster
    .............prod_cluster successfully deployed.
    

import { $ } from 'execa'

// Pipe input from a string or buffer, like <<< in Bash
await $({ input: 'dev_cluster' })`deploy`

// Pipe input from a file, like < in Bash
await $({ inputFile: 'clusters.txt' })`deploy`

// Pipe output to a file, like >, 2> and &> in Bash
await $`deploy dev_cluster`.pipeStdout('stdout.txt')
await $`deploy dev_cluster`.pipeStderr('stderr.txt')
await $({ all: true })`deploy dev_cluster`.pipeAll('logs.txt')

// Pipe output to another command, like | and |& in Bash
await $`deploy dev_cluster`.pipeStdout($`grep done`)

// Pipe output to a stream, like 2>&1 in Bash
const { stdout } = await $`deploy dev_cluster`.pipeStderr(process.stdout)