File: README.md
zx
#!/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
npm install zxAll setup options: zx/setup.
See also zx@lite.
Usage
Compatibility
- Linux, macOS, or Windows
- JavaScript Runtime:
- Node.js >= 12.17.0
- Bun >= 1.0.0
- Deno 1.x, 2.x
- GraalVM Node.js
- Bash or PowerShell
- Both CJS or ESM modules in JS or TS
See also
- MAML โ JSON for config, with comments, multiline strings, unquoted keys, and optional commas.
License
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.
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:
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:
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.
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.
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.
const p = $({timeout: '1s'})`sleep 999``$({nothrow})`
The nothrow option suppresses errors and returns a ProcessOutput with details.
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:
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.
cd('/tmp')
await $`pwd` // => /tmpLike echo, in addition to string arguments, cd accepts and trims
trailing newlines from ProcessOutput enabling common idioms like:
cd(await $`mktemp -d`)โ ๏ธ
cdinvokesprocess.chdir()internally, so it does affect the global context. To keepprocess.cwd()in sync with separate$calls enable syncProcessCwd() hook.
`fetch()`
A wrapper around the node-fetch-native
package.
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.
const p1 = fetch('https://example.com').pipe($`cat`)
const p2 = fetch('https://example.com').pipe`cat``question()`
A wrapper around the readline API.
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.
await sleep(1000)`echo()`
A console.log() alternative which can take ProcessOutput.
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.
const content = JSON.parse(await stdin())`within()`
Creates a new async context.
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'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().
import {syncProcessCwd} from 'zx'
syncProcessCwd()
syncProcessCwd(false) // pass false to disable the hookThis 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.
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.
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.
const packages = await glob(['package.json', 'packages/*/package.json'])
const markdowns = glob.sync('*.md') // sync API shortcut`which()`
The which package.
const node = await which('node')If nothrow option is used, returns null if not found.
const pathOrNull = await which('node', { nothrow: true })`ps`
The @webpod/ps package to provide a cross-platform way to list processes.
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.
await kill(123)
await kill(123, 'SIGKILL')`tmpdir()`
Creates a temporary directory.
t1 = tmpdir() // /os/based/tmp/zx-1ra1iofojgg/
t2 = tmpdir('foo') // /os/based/tmp/zx-1ra1iofojgg/foo/`tmpfile()`
Temp file factory.
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.
const argv = minimist(process.argv.slice(2), {})`argv`
A minimist-parsed version of the process.argv as argv.
if (argv.someFlag) {
echo('yes')
}Use minimist options to customize the parsing:
const myCustomArgv = minimist(process.argv.slice(2), {
boolean: [
'force',
'help',
],
alias: {
h: 'help',
},
})`chalk`
The chalk package.
console.log(chalk.blue('Hello world!'))`fs`
The fs-extra package.
const {version} = await fs.readJson('./package.json')`os`
The os package.
await $`cd ${os.homedir()} && mkdir example``path`
The path package.
await $`mkdir ${path.join(basedir, 'output')}``YAML`
The yaml package.
console.log(YAML.parse('foo: bar').foo)`MAML`
The maml package.
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.
// 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.
import { versions } from 'zx'
versions.zx // 8.7.2
versions.chalk // 5.4.1`quote()`
Default bash quoting function.
quote("$FOO") // "$'$FOO'"`quotePowerShell()`
PowerShell specific quoting.
quotePowerShell("$FOO") // "'$FOO'"`useBash()`
Enables bash preset: sets $.shell to bash and $.quote to quote.
useBash()`usePowerShell()`
Switches to PowerShell. Applies the quotePowerShell for quoting.
usePowerShell()`usePwsh()`
Sets pwsh (PowerShell v7+) as $.shell default.
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).
zx script.mjsnpx or node inits are valid too.
npx zx script.mjs
node -r zx/globals script.mjs
node --import zx/globals script.mjsNo 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.
zx script.zx # Unknown file extension "\.zx"
zx --ext=mjs script.zx # OKMarkdown
The CLI supports markdown files and interprets ts, js and bash code blocks as scripts.
zx docs/markdown.mdRemote scripts
If the argument to the zx executable starts with https://, the file will be
downloaded and executed.
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.
zx << 'EOF'
await $`pwd`
EOF`--eval`
Evaluate the following argument as a script.
cat package.json | zx --eval 'const v = JSON.parse(await stdin()).version; echo(v)'`--repl`
Starts zx in REPL mode.
`--install`
// script.mjs:
import sh from 'tinysh'
sh.say('Hello, world!')Add --install flag to the zx command to install missing dependencies
automatically.
zx --install script.mjsYou can also specify needed version by adding comment with @ after
the import.
import sh from 'tinysh' // @^1`--registry`
By default, zx uses https://registry.npmjs.org as a registry. Customize if needed.
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.
zx --shell=/bin/another/sh script.mjs`--prefer-local, -l`
Prefer locally installed packages and binaries.
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.
zx --prefix='echo foo;' --postfix='; echo bar' script.mjs`--cwd`
Set the current working directory.
zx --cwd=/foo/bar script.mjs`--env`
Specify an env file.
zx --env=/path/to/some.env script.mjsWhen 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.
ZX_VERBOSE=true ZX_SHELL='/bin/bash' zx script.mjssteps:
- 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).
const {version} = require('./package.json')File: docs/configuration.md
Configuration
`$.shell`
Specifies what shell is used. Default is which bash.
$.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.
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.
$.postfix = '; exit $LastExitCode' // for PowerShell compatibility`$.preferLocal`
Specifies whether to prefer node_modules/.bin located binaries over globally system installed ones.
$.preferLocal = true
await $`c8 npm test`You can also specify a directory to search for local binaries:
$.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.
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.
$.log.output = process.stdoutDefine $.log.formatters to customize each log entry kind printing:
$.log.formatters = {
cmd: (entry: LogEntry) => `CMD: ${entry.cmd}`,
fetch: (entry: LogEntry) => `FETCH: ${entry.url}`
}`$.timeout`
Specifies a timeout for the command execution.
$.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).
$.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.
$.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
- Install manually
- 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.
- Switch to the recommended version of Node.js
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 fmtto format your code.npm run test:coverageto 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
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:
const files = [...]
await $`tar cz ${files}`Importing into other scripts
It is possible to make use of $ and other functions via explicit imports:
#!/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.
$.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.
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 $`...`
EOFVerbose 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
// 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:
// 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.
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
#!/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
npm install zxor 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:
#!/usr/bin/env zxNow you will be able to run your script like so:
chmod +x ./script.mjs
./script.mjsOr via the CLI:
zx ./script.mjsAll functions ($, cd, fetch, etc) are available straight away
without any imports.
Or import globals explicitly (for better autocomplete in VS Code).
import 'zx/globals'``$`command` ``
Executes a given command using the spawn func
and returns ProcessPromise. It supports both sync and async modes.
const list = await $`ls -la`
const dir = $.sync`pwd`Everything passed through ${...} will be automatically escaped and quoted.
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:
const flags = [
'--oneline',
'--decorate',
'--color',
]
await $`git log ${flags}`In async mode, zx awaits any thenable in literal before executing the command.
const a1 = $`echo foo`
const a2 = new Promise((resolve) => setTimeout(resolve, 20, ['bar', 'baz']))
await $`echo ${a1} ${a2}` // foo bar bazIf the executed program returns a non-zero exit code,ProcessOutput will be thrown.
try {
await $`exit 1`
} catch (p) {
console.log(`Exit code: ${p.exitCode}`)
console.log(`Error: ${p.stderr}`)
}`ProcessOutput`
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.
const date = await $`date`
await $`echo Current date is ${date}.`License
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:
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.
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:
# 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 inzx: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:
zx script.mdHints
You can use imports here as well:
await import('chalk')js, javascript, ts, typescript, sh, shell, bash code blocks will be executed by zx.
VAR=$(date)
echo "$VAR" | wc -cOther kinds are ignored:
body .hero {
margin: 42px;
}The __filename will be pointed to markdown.md:
console.log(chalk.yellowBright(__filename))