zx Documentation

A tool for writing better scripts

45,635 stars JavaScript JavaScript 1 file ยท ~8,092 tokens #bash#child-process#cli#exec#javascript#nodejs#pipe#shell
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy zx on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name zx --restart always google/zx:latest

File: README.md

zx

js
#!/usr/bin/env zx

await $`cat package.json | grep name`

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

await Promise.all([
  $`sleep 1; echo 1`,
  $`sleep 2; echo 2`,
  $`sleep 3; echo 3`,
])

const name = 'foo bar'
await $`mkdir /tmp/${name}`

Bash is great, but when it comes to writing more complex scripts,
many people prefer a more convenient programming language.
JavaScript is a perfect choice, but the Node.js standard library
requires additional hassle before using. No compromise, take the best of both. The zx package provides
useful cross-platform wrappers around child_process, escapes arguments and
gives sensible defaults.

Install

bash
npm install zx

All setup options: zx/setup.
See also zx@lite.

Usage

Compatibility

See also

  • MAML โ€” JSON for config, with comments, multiline strings, unquoted keys, and optional commas.

License

Apache-2.0

Disclaimer: This is not an officially supported Google product.


File: docs/api.md

API Reference

`$.sync`

Zx provides both synchronous and asynchronous command executions, returns ProcessOutput or ProcessPromise respectively.

js
const list = await $`ls -la`
const dir = $.sync`pwd`

`$({...})`

$ object holds the default zx configuration, which is used for all execution. To specify a custom preset use $ as factory:

js
const $$ = $({
  verbose: false,
  env: {NODE_ENV: 'production'},
})

const env = await $$`node -e 'console.log(process.env.NODE_ENV)'`
const pwd = $$.sync`pwd`
const hello = $({quiet: true})`echo "Hello!"`

Moreover, presets are chainable:

js
const $1 = $({ nothrow: true })
assert.equal((await $1`exit 1`).exitCode, 1)

const $2 = $1({ sync: true }) // Both {nothrow: true, sync: true} are applied
assert.equal($2`exit 2`.exitCode, 2)

const $3 = $({ sync: true })({ nothrow: true })
assert.equal($3`exit 3`.exitCode, 3)

`$({input})`

The input option passes the specified stdin to the command.

js
const p1 = $({ input: 'foo' })`cat`
const p2 = $({ input: Readable.from('bar') })`cat`
const p3 = $({ input: Buffer.from('baz') })`cat`
const p4 = $({ input: p3 })`cat`
const p5 = $({ input: await p3 })`cat`

`$({signal})`

The signal option makes the process abortable.

js
const {signal} = new AbortController()
const p = $({ signal })`sleep 9999`

setTimeout(() => signal.abort('reason'), 1000)

`$({timeout})`

The timeout option makes the process autokillable after the specified delay.

js
const p = $({timeout: '1s'})`sleep 999`

`$({nothrow})`

The nothrow option suppresses errors and returns a ProcessOutput with details.

js
const o1 = await $({nothrow: true})`exit 1`
o1.ok       // false
o1.exitCode // 1
o1.message  // exit code: 1 ...

const o2 = await $({nothrow: true, spawn() { throw new Error('BrokenSpawn') }})`echo foo`
o2.ok       // false
o2.exitCode // null
o2.message  // BrokenSpawn ...

The full options list:

ts
interface Options {
  cwd:            string
  ac:             AbortController
  signal:         AbortSignal
  input:          string | Buffer | Readable | ProcessOutput | ProcessPromise
  timeout:        Duration
  timeoutSignal:  NodeJS.Signals
  stdio:          StdioOptions
  verbose:        boolean
  sync:           boolean
  env:            NodeJS.ProcessEnv
  shell:          string | true
  nothrow:        boolean
  prefix:         string
  postfix:        string
  quote:          typeof quote
  quiet:          boolean
  detached:       boolean
  preferLocal:    boolean | string | string[]
  spawn:          typeof spawn
  spawnSync:      typeof spawnSync
  store:          TSpawnStore
  log:            typeof log
  kill:           typeof kill
  killSignal:     NodeJS.Signals
  halt:           boolean
  delimiter:      string | RegExp
}

See also Configuration.

`cd()`

Changes the current working directory.

js
cd('/tmp')
await $`pwd` // => /tmp

Like echo, in addition to string arguments, cd accepts and trims
trailing newlines from ProcessOutput enabling common idioms like:

js
cd(await $`mktemp -d`)

โš ๏ธ cd invokes process.chdir() internally, so it does affect the global context. To keep process.cwd() in sync with separate $ calls enable syncProcessCwd() hook.

`fetch()`

A wrapper around the node-fetch-native
package.

js
const r1 = await fetch('https://example.com')
const json = await r1.json()

const r2 = await fetch('https://example.com', {
  signal: AbortSignal.timeout(5000),
})

For some cases, text() or json() can produce extremely large output that exceeds the string size limit.
Streams are just for that, so we've attached a minor adjustment to the fetch API to make it more pipe friendly.

js
const p1 = fetch('https://example.com').pipe($`cat`)
const p2 = fetch('https://example.com').pipe`cat`

`question()`

A wrapper around the readline API.

js
const bear = await question('What kind of bear is best? ')
const selected = await question('Select an option:', {
  choices: ['A', 'B', 'C'],
})

`sleep()`

A wrapper around the setTimeout function.

js
await sleep(1000)

`echo()`

A console.log() alternative which can take ProcessOutput.

js
const branch = await $`git branch --show-current`

echo`Current branch is ${branch}.`
// or
echo('Current branch is', branch)

`stdin()`

Returns the stdin as a string.

js
const content = JSON.parse(await stdin())

`within()`

Creates a new async context.

js
await $`pwd` // => /home/path
$.foo = 'bar'

within(async () => {
  $.cwd = '/tmp'
  $.foo = 'baz'

  setTimeout(async () => {
    await $`pwd` // => /tmp
    $.foo // baz
  }, 1000)
})

await $`pwd` // => /home/path
$.foo // still 'bar'
js
await $`node --version` // => v20.2.0

const version = await within(async () => {
  $.prefix += 'export NVM_DIR=$HOME/.nvm; source $NVM_DIR/nvm.sh; nvm use 16;'

  return $`node --version`
})

echo(version) // => v16.20.0

`syncProcessCwd()`

Keeps the process.cwd() in sync with the internal $ current working directory if it is changed via cd().

ts
import {syncProcessCwd} from 'zx'

syncProcessCwd()
syncProcessCwd(false) // pass false to disable the hook

This feature is disabled by default because of performance overhead.

`retry()`

Retries a callback for a few times. Will return the first
successful result, or will throw after the specified attempts count.

js
const p = await retry(10, () => $`curl https://medv.io`)

// With a specified delay between attempts.
const p = await retry(20, '1s', () => $`curl https://medv.io`)

// With an exponential backoff.
const p = await retry(30, expBackoff(), () => $`curl https://medv.io`)

`spinner()`

Starts a simple CLI spinner.

js
await spinner(() => $`long-running command`)

// With a message.
await spinner('working...', () => $`sleep 99`)

And it's disabled for CI by default.

`glob()`

The globby package.

js
const packages = await glob(['package.json', 'packages/*/package.json'])
const markdowns = glob.sync('*.md') // sync API shortcut

`which()`

The which package.

js
const node = await which('node')

If nothrow option is used, returns null if not found.

js
const pathOrNull = await which('node', { nothrow: true })

`ps`

The @webpod/ps package to provide a cross-platform way to list processes.

js
const all = await ps.lookup()
const nodejs = await ps.lookup({ command: 'node' })
const children = await ps.tree({ pid: 123 })
const fulltree = await ps.tree({ pid: 123, recursive: true })

`kill()`

A process killer.

js
await kill(123)
await kill(123, 'SIGKILL')

`tmpdir()`

Creates a temporary directory.

js
t1 = tmpdir()       // /os/based/tmp/zx-1ra1iofojgg/
t2 = tmpdir('foo')  // /os/based/tmp/zx-1ra1iofojgg/foo/

`tmpfile()`

Temp file factory.

js
f1 = tmpfile()         // /os/based/tmp/zx-1ra1iofojgg
f2 = tmpfile('f2.txt')  // /os/based/tmp/zx-1ra1iofojgg/foo.txt
f3 = tmpfile('f3.txt', 'string or buffer')
f4 = tmpfile('f4.sh', 'echo "foo"', 0o744) // executable

`minimist`

The minimist package.

js
const argv = minimist(process.argv.slice(2), {})

`argv`

A minimist-parsed version of the process.argv as argv.

js
if (argv.someFlag) {
  echo('yes')
}

Use minimist options to customize the parsing:

js
const myCustomArgv = minimist(process.argv.slice(2), {
  boolean: [
    'force',
    'help',
  ],
  alias: {
    h: 'help',
  },
})

`chalk`

The chalk package.

js
console.log(chalk.blue('Hello world!'))

`fs`

The fs-extra package.

js
const {version} = await fs.readJson('./package.json')

`os`

The os package.

js
await $`cd ${os.homedir()} && mkdir example`

`path`

The path package.

js
await $`mkdir ${path.join(basedir, 'output')}`

`YAML`

The yaml package.

js
console.log(YAML.parse('foo: bar').foo)

`MAML`

The maml package.

js
const maml = `{
  project: "MAML"
  tags: [ "minimal", "readable" ]

  # With support for comments
  spec: {
    version: 1
    author: "Anton Medvedev"
  }

  notes: """
This is a raw multiline string.
Keeps formatting asโ€‘is.
"""
}`
console.log(MAML.parse(maml).project) // MAML

`dotenv`

The envapi package.
An API to interact with environment vars in dotenv format.

js
// parse
const raw = 'FOO=BAR\nBAZ=QUX'
const data = dotenv.parse(raw) // {FOO: 'BAR', BAZ: 'QUX'}
await fs.writeFile('.env', raw)

// load
const env = dotenv.load('.env')
await $({ env })`echo $FOO`.stdout // BAR

// config
dotenv.config('.env')
process.env.FOO // BAR

`versions`

Exports versions of the zx dependencies.

ts
import { versions } from 'zx'

versions.zx     // 8.7.2
versions.chalk  // 5.4.1

`quote()`

Default bash quoting function.

js
quote("$FOO") // "$'$FOO'"

`quotePowerShell()`

PowerShell specific quoting.

js
quotePowerShell("$FOO") // "'$FOO'"

`useBash()`

Enables bash preset: sets $.shell to bash and $.quote to quote.

js
useBash()

`usePowerShell()`

Switches to PowerShell. Applies the quotePowerShell for quoting.

js
usePowerShell()

`usePwsh()`

Sets pwsh (PowerShell v7+) as $.shell default.

js
usePwsh()

File: docs/cli.md

CLI Usage

Zx provides a CLI for running scripts. It comes with the package and can be used as zx executable (if referenced in package.json "scripts", installed globally or added to the $PATH somehow).

sh
zx script.mjs

npx or node inits are valid too.

sh
npx zx script.mjs
node -r zx/globals script.mjs
node --import zx/globals script.mjs

No extensions

If the script does not have a file extension (like .git/hooks/pre-commit), zx
assumes that it is
an ESM
module unless the --ext option is specified.

Non-standard extension

zx internally loads scripts via import API, so you can use any extension supported by the runtime (nodejs, deno, bun) or apply a custom loader.
However, if the script has a non-js-like extension (/^\.[mc]?[jt]sx?$/) and the --ext is specified, it will be used.

bash
zx script.zx           # Unknown file extension "\.zx"
zx --ext=mjs script.zx # OK

Markdown

The CLI supports markdown files and interprets ts, js and bash code blocks as scripts.

bash
zx docs/markdown.md

Remote scripts

If the argument to the zx executable starts with https://, the file will be
downloaded and executed.

bash
zx https://raw.githubusercontent.com/google/zx/refs/heads/main/examples/hello.mjs

[!WARNING]
Make sure you trust the remote source and understand the code before running it.

Scripts from stdin

The zx supports executing scripts from stdin.

js
zx << 'EOF'
await $`pwd`
EOF

`--eval`

Evaluate the following argument as a script.

bash
cat package.json | zx --eval 'const v = JSON.parse(await stdin()).version; echo(v)'

`--repl`

Starts zx in REPL mode.

`--install`

js
// script.mjs:
import sh from 'tinysh'

sh.say('Hello, world!')

Add --install flag to the zx command to install missing dependencies
automatically.

bash
zx --install script.mjs

You can also specify needed version by adding comment with @ after
the import.

js
import sh from 'tinysh' // @^1

`--registry`

By default, zx uses https://registry.npmjs.org as a registry. Customize if needed.

bash
zx --registry=https://registry.yarnpkg.com script.mjs

`--quiet`

Suppress any outputs.

`--verbose`

Enable verbose mode.

`--shell`

Specify a custom shell binary path. By default, zx refers to bash.

bash
zx --shell=/bin/another/sh script.mjs

`--prefer-local, -l`

Prefer locally installed packages and binaries.

bash
zx --prefer-local=/external/node_modules/or/nm-root script.mjs

`--prefix & --postfix`

Attach a command to the beginning or the end of every command.

bash
zx --prefix='echo foo;' --postfix='; echo bar' script.mjs

`--cwd`

Set the current working directory.

bash
zx --cwd=/foo/bar script.mjs

`--env`

Specify an env file.

bash
zx --env=/path/to/some.env script.mjs

When cwd option is specified, it will be used as base path:
--cwd='/foo/bar' --env='../.env' โ†’ /foo/.env

`--ext`

Overrides the default script extension (.mjs).

`--version, -v`

Print the current zx version.

`--help, -h`

Print help notes.

Environment variables

All the previously mentioned options can be set via the corresponding ZX_-prefixed environment variables.

bash
ZX_VERBOSE=true ZX_SHELL='/bin/bash' zx script.mjs
yaml
steps:
  - name: Run script
    run: zx script.mjs
    env:
      ZX_VERBOSE: true
      ZX_SHELL: '/bin/bash'

`__filename & __dirname`

In ESM modules, Node.js does not provide
__filename and __dirname globals. As such globals are really handy in scripts,
zx provides these for use in .mjs files (when using the zx executable).

`require()`

In ESM
modules, the require() function is not defined.
The zx provides require() function, so it can be used with imports in .mjs
files (when using zx executable).

js
const {version} = require('./package.json')

File: docs/configuration.md

Configuration

`$.shell`

Specifies what shell is used. Default is which bash.

js
$.shell = '/usr/bin/bash'

Or use a CLI argument: --shell=/bin/bash

`$.spawn`

Specifies a spawn api. Defaults to native child_process.spawn.

To override a sync API implementation, set $.spawnSync correspondingly.

`$.kill`

Specifies a kill function. The default implements half-graceful shutdown via ps.tree(). You can override with more sophisticated logic.

js
import treekill from 'tree-kill'

$.kill = (pid, signal = 'SIGTERM') => {
  return new Promise((resolve, reject) => {
    treekill(pid, signal, (err) => {
      if (err) reject(err)
      else resolve()
    })
  })
}

`$.prefix`

Specifies the command that will be prefixed to all commands run.

Default is set -euo pipefail;.

Or use a CLI argument: --prefix='set -e;'

`$.postfix`

Like a $.prefix, but for the end of the command.

js
$.postfix = '; exit $LastExitCode' // for PowerShell compatibility

`$.preferLocal`

Specifies whether to prefer node_modules/.bin located binaries over globally system installed ones.

js
$.preferLocal = true

await $`c8 npm test`

You can also specify a directory to search for local binaries:

js
$.preferLocal = '/some/to/bin'
$.preferLocal = ['/path/to/bin', '/another/path/bin']

`$.quote`

Specifies a function for escaping special characters during
command substitution.

`$.verbose`

Specifies verbosity. Default is false.

In verbose mode, zx prints all executed commands alongside with their
outputs.

Or use the CLI argument: --verbose to set true.

`$.quiet`

Suppresses all output. Default is false.

Via CLI argument: --quiet sets $.quiet = true.

`$.env`

Specifies an environment variables map.

Defaults to process.env.

`$.cwd`

Specifies a current working directory of all processes created with the $.

The cd() func changes only process.cwd() and if no $.cwd specified,
all $ processes use process.cwd() by default (same as spawn behavior).

`$.log`

Specifies a logging function.

ts
import {LogEntry, log} from 'zx/core'

$.log = (entry: LogEntry) => {
  switch (entry.kind) {
    case 'cmd':
      // for example, apply custom data masker for cmd printing
      process.stderr.write(masker(entry.cmd))
      break
    default:
      log(entry)
  }
}

The log mostly acts like a debugger, so by default it uses process.error for output.
Override the $.log.output to change the stream.

ts
$.log.output = process.stdout

Define $.log.formatters to customize each log entry kind printing:

ts
$.log.formatters = {
  cmd: (entry: LogEntry) => `CMD: ${entry.cmd}`,
  fetch: (entry: LogEntry) => `FETCH: ${entry.url}`
}

`$.timeout`

Specifies a timeout for the command execution.

js
$.timeout = '1s'
$.timeoutSignal= 'SIGKILL'

await $`sleep 999`

`$.delimiter`

Specifies a delimiter for splitting command output into lines.
Defaults to \r?\n (newline or carriage return + newline).

js
$.delimiter = /\0/        // null character

await $`find ./ -type f -print0 -maxdepth 1`

`$.defaults`

Holds default configuration values. They will be used if the corresponding
$ options are not specified.

ts
$.defaults = {
  cwd:            process.cwd(),
  env:            process.env,
  verbose:        false,
  quiet:          false,
  sync:           false,
  shell:          true,
  prefix:         'set -euo pipefail;',   // for bash
  postfix:        '; exit $LastExitCode', // for powershell
  nothrow:        false,
  stdio:          'pipe', // equivalent to ['pipe', 'pipe', 'pipe']
  detached:       false,
  preferLocal:    false,
  spawn:          childProcess.spawn,
  spawnSync:      childProcess.spawnSync,
  log:            $.log,
  kill:           $.kill,
  killSignal:     'SIGTERM',
  timeoutSignal:  'SIGTERM',
  delimiter:      /\r?\n/,
}

File: docs/contribution.md

Contribution Guide

zx is a fully open-source project, which is developing by the community for the community.
We welcome contributions of any kind, including but not limited to:

  • Bug reports
  • Feature requests
  • Code contributions
  • Documentation improvements
  • Discussions

https://google.github.io/zx/contribution

Community Guidelines

This project follows Google's Open Source Community Guidelines.
In short: all contributors are treated with respect and fairness.

Contributor License Agreement

Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution;
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to https://cla.developers.google.com/ to see
your current agreements on file or to sign a new one.

You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.

How to Contribute

Before proposing changes, look for similar ones in the project's issues and pull requests. If you can't decide, create a new discussion topic, and we will help you figure it out. Dive also into architecture notes to observe design concepts. When ready to move on:

  • Prepare your development environment.

    • Switch to the recommended version of Node.js
      • Install manually Node.js >= 22.
      • Delegate the routine to any version manager, that supports .node_version config
      • Use Volta, the target version will be set automatically from the package.json
    • Bash is essential for running zx scripts. Linux and macOS users usually have it installed by default. Consider using Windows Subsystem for Linux or Git Bash if you are on Windows.
  • Fork the repository.

  • Create a new branch.

  • Make your changes.

    • If you are adding a new feature, please include additional tests. The coverage threshold is 98%.
    • Create a conventional-commits compliant messages.
  • Ensure that everything is working:

    • npm run fmt to format your code.
    • npm run test:coverage to run the tests.
  • Push the changes to your fork.

  • Create a pull request.

    • Describe your changes in detail.
    • Reference any related issues if applicable.

Code Reviews

All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult GitHub Help for more information on using pull requests.

License

The project is licensed under the Apache-2.0


File: docs/faq.md

FAQ

Passing env variables

js
process.env.FOO = 'bar'
await $`echo $FOO`

Passing array of values

When passing an array of values as an argument to $, items of the array will
be escaped
individually and concatenated via space.

Example:

js
const files = [...]
await $`tar cz ${files}`

Importing into other scripts

It is possible to make use of $ and other functions via explicit imports:

js
#!/usr/bin/env node
import {$} from 'zx'

await $`date`

Attaching a profile

By default child_process does not include aliases and bash functions.
But you are still able to do it by hand. Just attach necessary directives
to the $.prefix.

js
$.prefix += 'export NVM_DIR=$HOME/.nvm; source $NVM_DIR/nvm.sh; '
await $`nvm -v`

Using GitHub Actions

The default GitHub Action runner comes with npx installed.

yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
#      - uses: actions/setup-node@v4
#        with:
#          node-version: 22

      - name: Build with zx
        env:
          FORCE_COLOR: 3
        run: |
          npx zx <<'EOF'
          await $`...`
          EOF

Verbose and Quiet

zx has internal logger, which captures events if a condition is met:

Event Verbose Quiet Description
stdout true false Spawned process stdout
stderr any false Process stderr data
cmd true false Command execution
fetch true false Fetch resources by http(s)
cd true false Change directory
retry true false Capture exec error
custom true false User-defined event

By default, both $.verbose and $.quiet options are false, so only stderr events are written. Any output goes to the process.stderr stream.

You may control this flow globally or in-place

js
// Global debug mode on
$.verbose = true
await $`echo hello`

// Suppress the particular command
await $`echo fobar`.quiet()

// Suppress everything
$.quiet = true
await $`echo world`

// Turn on in-place debug
await $`echo foo`.verbose()

You can also override the default logger with your own:

js
// globally
$.log = (entry) => {
  switch (entry.kind) {
    case 'cmd':
      console.log('Command:', entry.cmd)
      break
    default:
      console.warn(entry)
  }
}
// or in-place
$({log: () => {}})`echo hello`

Canary / Beta / RC builds

Impatient early adopters can try the experimental zx versions.
But keep in mind: these builds are โš ๏ธ๏ธbeta in every sense.

bash
npm i zx@dev
npx zx@dev --install --quiet <<< 'import _ from "lodash" /* 4.17.15 */; console.log(_.VERSION)'

File: docs/getting-started.md

Getting Started

Overview

js
#!/usr/bin/env zx

await $`cat package.json | grep name`

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

await Promise.all([
  $`sleep 1; echo 1`,
  $`sleep 2; echo 2`,
  $`sleep 3; echo 3`,
])

const name = 'foo bar'
await $`mkdir /tmp/${name}`

Bash is great, but when it comes to writing more complex scripts,
many people prefer a more convenient programming language.
JavaScript is a perfect choice, but the Node.js standard library
requires additional hassle before using. The zx package provides
useful wrappers around child_process, escapes arguments and
gives sensible defaults.

Install

bash
npm install zx

or many other ways

Usage

Write your scripts in a file with an .mjs extension in order to
use await at the top level. If you prefer the .js extension,
wrap your scripts in something like void async function () {...}(). TypeScript is also supported.

Add the following shebang to the beginning of your zx scripts:

bash
#!/usr/bin/env zx

Now you will be able to run your script like so:

bash
chmod +x ./script.mjs
./script.mjs

Or via the CLI:

bash
zx ./script.mjs

All functions ($, cd, fetch, etc) are available straight away
without any imports.

Or import globals explicitly (for better autocomplete in VS Code).

js
import 'zx/globals'

``$`command` ``

Executes a given command using the spawn func
and returns ProcessPromise. It supports both sync and async modes.

js
const list = await $`ls -la`
const dir = $.sync`pwd`

Everything passed through ${...} will be automatically escaped and quoted.

js
const name = 'foo & bar'
await $`mkdir ${name}`

There is no need to add extra quotes. Read more about it in
quotes.

You can pass an array of arguments if needed:

js
const flags = [
  '--oneline',
  '--decorate',
  '--color',
]
await $`git log ${flags}`

In async mode, zx awaits any thenable in literal before executing the command.

js
const a1 = $`echo foo`
const a2 = new Promise((resolve) => setTimeout(resolve, 20, ['bar', 'baz']))

await $`echo ${a1} ${a2}` // foo bar baz

If the executed program returns a non-zero exit code,
ProcessOutput will be thrown.

js
try {
  await $`exit 1`
} catch (p) {
  console.log(`Exit code: ${p.exitCode}`)
  console.log(`Error: ${p.stderr}`)
}

`ProcessOutput`

ts
class ProcessOutput {
  readonly stdout: string
  readonly stderr: string
  readonly signal: string
  readonly exitCode: number
  // ...
  toString(): string // Combined stdout & stderr.
  valueOf(): string  // Returns .toString().trim()
}

The output of the process is captured as-is. Usually, programs print a new
line \n at the end.
If ProcessOutput is used as an argument to some other $ process,
zx will use stdout and trim the new line.

js
const date = await $`date`
await $`echo Current date is ${date}.`

License

Apache-2.0

Disclaimer: This is not an officially supported Google product.


File: docs/index.md


https://vitepress.dev/reference/default-theme-home-page

layout: home
titleTemplate: google/zx

hero:
name: "zx"
text: "A tool for writing better scripts"
image:
src: /img/logo.svg
alt: Zx Logo
actions:
- theme: brand
text: Documentation
link: /getting-started

features:

  • title: Simple
    details: Write your scripts in a familiar language.
  • title: Powerful
    details: Interact with the full ecosystem of JS libraries.
  • title: Batteries included
    details: Everything you need, right out of the box.


File: docs/known-issues.md

Known Issues

Output gets truncated

This is a known issue with console.log() (see nodejs/node#6379).
It's caused by different behaviour of console.log() writing to the terminal vs
to a file. If a process calls process.exit(), buffered output will be truncated.
To prevent this, the process should use process.exitCode = 1 and wait for the
process to exit itself. Or use something like exit package.

Workaround is to write to a temp file:

js
const tmp = await $`mktemp` // Creates a temp file.
const {stdout} = await $`cmd > ${tmp}; cat ${tmp}`

Colors in subprocess

You may see what tools invoked with await $ don't show colors, compared to
what you see in a terminal. This is because, the subprocess does not think it's
a TTY and the subprocess turns off colors. Usually there is a way force
the subprocess to add colors.

js
process.env.FORCE_COLOR='1'
await $`cmd`

File: docs/markdown.md

Markdown Scripts

Imagine a script with code blocks, formatted comments, schemas, illustrations, etc. Markdown is right for this purpose.
Combine ts, js, bash sections to produce a single zx scenario. For example:

text
# Some script
`ls` โ€” is an unix command to get directory contents. Let's see how to use it in `zx`:

```js
// ts, js, cjs, mjs, etc
const {stdout} = await $`ls -l`
console.log('directory contents:', stdout)
```

This part invokes the same command in a different way:
```bash
# bash syntax
ls -l
```

And how it looks like:

Some script

ls โ€” is an unix command to get directory contents. Let's see how to use it in zx:

js
// ts, js, cjs, mjs, etc
const {stdout} = await $`ls -l`
console.log('directory contents:', stdout)

This part invokes the same command in a different way:

bash
# bash syntax
ls -l

The rest is simple: just run via zx command:

bash
zx script.md

Hints

You can use imports here as well:

js
await import('chalk')

js, javascript, ts, typescript, sh, shell, bash code blocks will be executed by zx.

bash
VAR=$(date)
echo "$VAR" | wc -c

Other kinds are ignored:

css
body .hero {
  margin: 42px;
}

The __filename will be pointed to markdown.md:

js
console.log(chalk.yellowBright(__filename))