Scripts sans shell avec Execa 7

Mar 12 2023
Alors que les scripts shell sont très pratiques, de nombreux développeurs apprécient la flexibilité, l'expressivité et l'écosystème qui accompagnent les langages de programmation comme JavaScript. zx, un récent projet populaire de Google, combine le meilleur des deux mondes.
Plage presque sans coquillages (photo d'Aaron Burden)

Alors que les scripts shell sont très pratiques, de nombreux développeurs apprécient la flexibilité, l'expressivité et l'écosystème qui accompagnent les langages de programmation comme JavaScript. zx, un récent projet populaire de Google, combine le meilleur des deux mondes.

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}`

Pourquoi utiliser à la fois Node et Bash ? Avec Execa, il n'y a pas de syntaxe shell à retenir : tout est en JavaScript. Presque toutes les fonctionnalités spécifiques au shell peuvent être exprimées en JavaScript. Pour les cas extrêmes restants, une shelloption est disponible.

C'est plus :

  • Sécurisé : les injections d'obus ne sont techniquement pas réalisables.
  • Multiplateforme : toutes les machines Windows ne disposent pas de Bash.
  • Performant : générer un shell pour chaque commande a un coût. Même npxpeut être ignoré car Execa peut exécuter directement des fichiers binaires installés localement.
  • # 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 },
)

Le child_processmodule Core Node comprend de nombreuses fonctionnalités utiles : délai d'expiration, annulation, processus d'arrière-plan , IPC , PID , UID/GID, références faibles , etc. Execa en ajoute quelques- unes supplémentaires : fin progressive , nettoyage , sortie entrelacée , etc. Celles-ci peuvent être définies à l'aide $(options)d'une ou de plusieurs commandes.

import { $ } from 'execa'

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

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

Les processus enfants peuvent être difficiles à déboguer. C'est pourquoi Execa :

  • Inclut une verboseoption , qui peut être définie à l'aide de NODE_DEBUG=execa.
  • Signale les messages d'erreur et les propriétés détaillés .
  • Est purement sans état, ce qui permet de déterminer facilement quel est le répertoire actuel ou toute autre option.
  • > 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)