zx

repository·main·Indexed 13 days ago

https://github.com/google/zx

A tool for writing better scripts that brings the power of JavaScript and TypeScript to shell scripting. It provides cross-platform wrappers around child_process, automatic argument escaping, and a powerful $ template literal for executing shell commands. Version 8.9.0 supports Node.js >= 12.17.0, Bun >= 1.0.0, and Deno 1.x/2.x.

Tokens
22K
Snippets
99
Records
109
Agent score
97%

What's inside zx

  1. Handle process output with ProcessOutput

    main

    When you await a command executed via the $ template literal, it returns a ProcessOutput object. This object contains the results of the command execution, including exit codes, signals, and the captured output streams (stdout and stderr).

    To access the output, you must await the ProcessPromise returned by the $ command.

    const p = $"command" // This is a ProcessPromise
    const o = await p     // This is the ProcessOutput
  2. How the `$` shell factory works

    main

    The $ symbol is a template literal factory used to execute shell commands. It can be used in two primary modes:

    1. Asynchronous (Default): Returns a ProcessPromise. This is the standard way to run commands that you want to await.
    2. Synchronous: Using $.sync, it returns a ProcessOutput object immediately, blocking execution until the command completes.

    $ uses AsyncLocalStorage to manage configuration context, allowing you to pass options that are automatically applied to commands within that context.

    // Asynchronous (returns ProcessPromise)
    await $`cmd ${arg}`
    $(opts)`cmd ${arg}`
    
    // Synchronous (returns ProcessOutput)
    $.sync`cmd ${arg}`
    $.sync(opts)`cmd ${arg}`
  3. Understand zx entry points

    main

    zx exports several entry points tailored for different integration patterns:

    • zx: The main entry point providing all features.
    • zx/global: Populates the global scope with zx functions (useful for scripts that don't use imports).
    • zx/cli: Designed for running zx scripts directly from the command line.
    • zx/core: Provides the zx template spawner for use within 3rd party libraries with a reduced set of utilities.
  4. How quoting works in zx

    main

    Zx uses C-style quotes ($'...') for shell arguments. When using the template literal syntax $ with ${...}, zx automatically escapes and quotes the interpolated values.

    Warning: Because zx automatically escapes everything inside ${...}, you should not add manual quotes around interpolated variables. Doing so can lead to unexpected results or unsafe injections.

    For example, if you have a variable containing spaces or special characters, simply interpolate it directly:

    const name = 'foo & bar'
    await $`mkdir ${name}`
  5. Use `ProcessPromise` for command lifecycle and piping

    main

    A ProcessPromise represents a running child process. It inherits from Promise and provides advanced capabilities for managing command execution:

    Lifecycle Stages

    • initial: Blank instance.
    • halted: Awaiting execution.
    • running: Process is currently active.
    • fulfilled: Successfully completed.
    • rejected: Failed.

    Piping Commands

    You can pipe the output of one command into another using the .pipe property. This allows for powerful stream manipulation:

    const p = $`cmd`
    const crits = await p.pipe.stderr`grep critical`
    const names = await p.pipe.stdout`grep name`

    The "Wayback Machine" Pattern

    zx allows you to pipe commands even after the original process has settled (finished), preventing data loss by internally recording previous output.

    const p = $`cmd`
    await p
    await p.pipe`grep name` // Works even though `p` is already settled
  6. Understand the `zx` CLI architecture

    main

    The zx CLI includes a script preprocessor that constructs an execution context (applying presets and injecting global variables) and automatically installs required dependencies before running your script.

    Key internal helpers used by the CLI:

    • main(): Initializes presets from flags and environment variables.
    • readScript(): Fetches and transforms source code (supports stdin, https, and md transformations).
    • runScript(): Executes the transformed script via async import().
  7. Choose between @latest and @lite versions of zx

    main

    zx is distributed in different versions depending on your needs for features and bundle size:

    • @latest: The stable, full-featured version. It includes all zx/globals, the zx/cli, and all utility extensions (like fetch, fs, glob, spinner, etc.). Use this for most scripting tasks.
    • @lite: A lightweight version that separates the core from the extensions. It includes only the essential primitives like $ (ProcessPromise), cd, chalk, log, and shell configuration (useBash, usePowerShell, usePwsh). Use this when you want to minimize dependencies or are building a custom environment.
    • @dev: Contains experimental snapshots and Release Candidates (RCs).
  8. Understand the ProcessPromise abstraction

    main

    The $ operator returns a ProcessPromise instance, which inherits from the native Promise. When a ProcessPromise is resolved (e.g., via await), it becomes a ProcessOutput object.

    By default, $ spawns a process immediately. To delay execution and trigger it manually, use the {halt: true} option and call .run().

    Stages of a ProcessPromise include:

    • initial
    • halted
    • running
    • fulfilled
    • rejected
    const p = $`command` // Returns ProcessPromise
    const o = await p    // Resolves to ProcessOutput
    
    // Delayed execution
    const p = $({halt: true})`command` 
    const o = await p.run()
    
    // Checking stage
    p.stage // 'running'
    await p
    p.stage // 'fulfilled'
  9. Attach a shell profile or aliases to $.prefix

    main

    By default, child_process does not include shell aliases or functions. To use them, you can manually append the necessary shell directives (like source or export commands) to the $.prefix property.

    $.prefix += 'export NVM_DIR=$HOME/.nvm; source $NVM_DIR/nvm.sh; '
    await $`nvm -v`
  10. Create Markdown Scripts with zx

    main

    You can write scripts directly in Markdown files by combining multiple code blocks. zx will execute specific language blocks while treating the rest of the Markdown as documentation. This allows you to mix prose, illustrations, and schemas with executable code.

    Supported executable code blocks include:

    • js, javascript, ts, typescript (for JavaScript/TypeScript logic)
    • sh, shell, bash (for shell commands)

    All other code blocks (e.g., css, json) are ignored by the executor and treated as plain text/documentation.

    # My Script
    
    This is a description of my task.
    
    ```js
    // This code will run
    const {stdout} = await $`ls -l`
    console.log(stdout)
    # This command will also run
    ls -l
  11. Set up and run zx scripts

    main

    To write zx scripts, use an .mjs extension to enable top-level await. Add the zx shebang to the top of your file to run it as an executable.

    Steps:

    1. Add #!/usr/bin/env zx to the first line.
    2. Make the file executable with chmod +x.
    3. Run the script directly or via the zx CLI.

    If using .js instead of .mjs, wrap your code in an async function (e.g., void async function () { ... }()).

    #!/usr/bin/env zx
    
    await $`echo hello`
    chmod +x ./script.mjs
    ./script.mjs
    # OR
    zx ./script.mjs