Scripts sem shell com Execa 7

Mar 12 2023
Embora os scripts de shell sejam muito úteis, muitos desenvolvedores aproveitam a flexibilidade, a expressividade e o ecossistema que vêm com linguagens de programação como JavaScript. zx, um recente projeto popular do Google, combina o melhor dos dois mundos.
Praia quase sem conchas (foto de Aaron Burden)

Embora os scripts de shell sejam muito úteis, muitos desenvolvedores aproveitam a flexibilidade, a expressividade e o ecossistema que vêm com linguagens de programação como JavaScript. zx, um recente projeto popular do Google, combina o melhor dos dois 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 que usar Node e Bash? Com Execa, não há sintaxe de shell para lembrar: tudo é simplesmente JavaScript. Quase todos os recursos específicos do shell podem ser expressos em JavaScript. Para os casos extremos restantes, uma shellopção está disponível.

Isso é mais:

  • Seguro : as injeções de shell não são tecnicamente viáveis.
  • Plataforma cruzada : nem todas as máquinas Windows têm o Bash disponível.
  • Desempenho : gerar um shell para cada comando tem um custo. Even npxpode ser ignorado, pois o Execa pode executar diretamente binários 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 },
)

O child_processmódulo principal do Node inclui muitos recursos úteis: tempo limite, cancelamento, processos em segundo plano , IPC , PID , UID/GID, referências fracas e muito mais. O Execa adiciona alguns adicionais : finalização normal , limpeza , saída intercalada , etc. Esses podem ser configurados usando $(options)para um ou vários comandos.

import { $ } from 'execa'

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

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

Os processos filho podem ser difíceis de depurar. É por isso que a Execa:

  • Inclui uma verboseopção , que pode ser definida usando NODE_DEBUG=execa.
  • Relata propriedades e mensagens de erro detalhadas .
  • É puramente sem estado, tornando simples descobrir qual é o diretório atual ou qualquer outra opção.
  • > 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)