{"skill":{"slug":"zx","displayName":"zx","summary":"Comprehensive guide for writing shell scripts with Google zx — a tool for writing better scripts using JavaScript/TypeScript. Use when writing, debugging, or...","description":"---\r\nname: zx\r\ndescription: Comprehensive guide for writing shell scripts with Google zx — a tool for writing better scripts using JavaScript/TypeScript. Use when writing, debugging, or refactoring zx scripts (.mjs, .js, .ts files using zx), executing shell commands from JavaScript, working with ProcessPromise/ProcessOutput APIs, piping streams, configuring zx options, or using zx CLI.  Do NOT use for general Node.js questions unrelated to shell scripting.\r\n---\r\n\r\n# Zx — Write Better Shell Scripts with JavaScript\r\n\r\n## Overview\r\n\r\nzx is Google's tool for writing shell scripts in JavaScript/TypeScript. It wraps `child_process`, auto-escapes arguments, and provides sensible defaults — giving you the power of the JavaScript ecosystem in your scripts.\r\n\r\n```js\r\n#!/usr/bin/env zx\r\n\r\nawait $`cat package.json | grep name`\r\n\r\nconst branch = await $`git branch --show-current`\r\nawait $`dep deploy --branch=${branch}`\r\n\r\nconst name = 'foo & bar'\r\nawait $`mkdir /tmp/${name}`  // No quotes needed — auto-escaped\r\n```\r\n\r\nBash is great for simple tasks, but when scripts grow complex, a full programming language helps. zx adds helpful wrappers around `child_process`, escapes arguments, and gives sensible defaults. **Think: bash + JavaScript in one script.**\r\n\r\n## Triggers\r\n\r\nAlso triggers when users ask about running shell commands in JavaScript, converting bash scripts to zx, executing remote scripts, Markdown scripts, or TypeScript shell scripts.\r\n\r\n## Quick Start\r\n\r\n```bash\r\nnpm install zx\r\n```\r\n\r\nWrite scripts as `.mjs` files (supports top-level `await`). Add `#!/usr/bin/env zx` shebang or run via CLI:\r\n\r\n```bash\r\nzx ./script.mjs           # Direct execution\r\nnpx zx ./script.mjs       # Via npx\r\nnode --import zx/globals  # As Node.js loader\r\n```\r\n\r\nAll functions (`$`, `cd`, `fetch`, etc.) are globally available in zx scripts without imports. For explicit imports (better VS Code autocomplete):\r\n\r\n```js\r\nimport 'zx/globals'\r\n```\r\n\r\n## Core Concepts\r\n\r\n### `` $`command` `` — Execute Shell Commands\r\n\r\nThe tagged template literal is the heart of zx. Everything in `${...}` is auto-escaped and quoted.\r\n\r\n```js\r\n// Async (standard) — returns ProcessPromise\r\nconst output = await $`ls -la`\r\n\r\n// Sync variant — returns ProcessOutput directly\r\nconst dir = $.sync`pwd`\r\n\r\n// Arrays are flattened\r\nconst flags = ['--oneline', '--decorate', '--color']\r\nawait $`git log ${flags}`\r\n\r\n// Non-zero exit codes throw ProcessOutput\r\ntry {\r\n  await $`exit 1`\r\n} catch (p) {\r\n  console.log(`Exit: ${p.exitCode}, Error: ${p.stderr}`)\r\n}\r\n```\r\n\r\n### Preset Configuration with `$({...})`\r\n\r\nCreate custom `$` instances with preset options — chainable and composable:\r\n\r\n```js\r\nconst $$ = $({ verbose: false, env: { NODE_ENV: 'production' } })\r\nconst pwd = $$.sync`pwd`\r\n\r\n// Presets are chainable\r\nconst $1 = $({ nothrow: true })\r\nconst $2 = $1({ sync: true })  // Both nothrow + sync applied\r\n```\r\n\r\n### ProcessPromise & ProcessOutput\r\n\r\n```\r\n$`cmd`              ProcessPromise (extends Promise)\r\n  ├── .pipe()       Stream piping\r\n  ├── .kill()       Terminate process\r\n  ├── .text()       Output as string\r\n  ├── .json()       Output as parsed JSON\r\n  ├── .lines()      Output split by lines\r\n  ├── .nothrow()    Suppress errors for this command\r\n  ├── .quiet()      Suppress output for this command\r\n  ├── .timeout()    Auto-kill after duration\r\n  ├── .stdio()      Configure I/O\r\n  ├── .exitCode     Promise<exit code>\r\n  ├── .stdout       Readable stream\r\n  ├── .stderr       Readable stream\r\n  ├── .stdin        Writable stream\r\n  ├── .pid / .cmd   Process metadata\r\n  └── await → ProcessOutput\r\n                ├── .stdout    string\r\n                ├── .stderr    string\r\n                ├── .exitCode  number\r\n                ├── .signal    string|null\r\n                ├── .text() / .json() / .lines() / .buffer() / .blob()\r\n                └── .ok        boolean (when nothrow)\r\n```\r\n\r\n## Decision Tree\r\n\r\nWhen writing zx scripts, use this decision tree:\r\n\r\n| Goal | Approach |\r\n|------|----------|\r\n| Run a command | `` await $`cmd` `` |\r\n| Run synchronously | `` $.sync`cmd` `` |\r\n| Pipe output | `` .pipe($`next`) `` / `` .pipe('file.txt') `` |\r\n| Handle errors gracefully | `` $({nothrow: true}) `` / `` .nothrow() `` |\r\n| Set timeout | `` $({timeout: '30s'}) `` / `` .timeout('30s') `` |\r\n| Parse JSON output | `` (await $`cmd`).json() `` |\r\n| Real-time streaming | `` for await (const line of $`cmd`) `` |\r\n| Retry on failure | `` retry(5, () => $`cmd`) `` |\r\n| User prompt | `` question('Name: ') `` |\r\n| Progress indicator | `` await spinner('Working...', () => $`cmd`) `` |\r\n| Change directory | `` cd('/path') `` or `` within(() => { $.cwd = '/tmp' }) `` |\r\n| Temp files/dirs | `` tmpfile() `` / `` tmpdir() `` |\r\n| Parse CLI args | `` argv.flag `` or `` minimist(process.argv.slice(2)) `` |\r\n| Load .env file | `` dotenv.config('.env') `` |\r\n\r\n## Writing Effective zx Scripts\r\n\r\n### Parallel Execution\r\n\r\n```js\r\nconst results = await Promise.all([\r\n  $`sleep 1; echo 1`,\r\n  $`sleep 2; echo 2`,\r\n  $`sleep 3; echo 3`,\r\n])\r\n```\r\n\r\n### Error Handling with nothrow\r\n\r\n```js\r\n$.nothrow = true\r\n\r\nconst repos = ['zx', 'webpod']\r\nconst clones = repos.map(n => $`git clone https://github.com/google/${n}`)\r\n\r\nconst results = await Promise.all(clones)\r\nconst errors = results.filter(o => !o.ok).map(o => o.stderr.trim())\r\nconsole.log('Errors:', errors.join('\\n'))\r\n```\r\n\r\n### Stream Piping\r\n\r\n```js\r\n// Chain commands like bash pipes\r\nconst greeting = await $`printf \"hello\"`\r\n  .pipe($`awk '{printf $1\", world!\"}'`)\r\n  .pipe($`tr '[a-z]' '[A-Z]'`)\r\n\r\n// Pipe to file\r\nawait $`echo \"Hello!\"`.pipe('/tmp/output.txt')\r\n\r\n// Real-time output to terminal\r\nawait $`echo 1; sleep 1; echo 2; sleep 1; echo 3`.pipe(process.stdout)\r\n```\r\n\r\n### Stream Splitting & Merging\r\n\r\n```js\r\n// Split one source to multiple consumers\r\nconst p = $`some-command`\r\nconst [o1, o2] = await Promise.all([\r\n  p.pipe`log`,\r\n  p.pipe`extract`,\r\n])\r\n\r\n// Merge multiple sources\r\nconst $h = $({ halt: true })\r\nconst p1 = $`echo foo`\r\nconst p2 = $h`echo a && sleep 0.1 && echo b`\r\nconst p3 = $h`echo c && sleep 0.1 && echo d`\r\nconst cat = $h`cat`\r\np1.pipe(cat); p2.pipe(cat); p3.pipe(cat)\r\nawait cat.run()\r\n```\r\n\r\n### Output Formatters\r\n\r\n```js\r\nconst p = $`echo '{\"foo\":\"bar\"}\\nline2'`\r\n\r\nawait p.json()    // { foo: 'bar' }\r\nawait p.lines()   // ['{\"foo\":\"bar\"}', 'line2']\r\nawait p.text()    // '{\"foo\":\"bar\"}\\nline2\\n'\r\n```\r\n\r\n### Async Iteration\r\n\r\n```js\r\nfor await (const line of $`git log --oneline --max-count=5`) {\r\n  console.log(line)\r\n}\r\n```\r\n\r\n## Shell Configuration\r\n\r\nzx defaults to bash. Switch shells as needed:\r\n\r\n```js\r\nimport { useBash, usePowerShell, usePwsh } from 'zx'\r\n\r\nusePowerShell()  // PowerShell.exe\r\nusePwsh()        // PowerShell v7+\r\nuseBash()        // Back to bash\r\n\r\n// Manual override\r\n$.shell = '/bin/zsh'\r\n```\r\n\r\nOn Windows, consider using WSL or Git Bash for bash support, or switch to PowerShell via `usePowerShell()`.\r\n\r\n## Built-in Helpers Summary\r\n\r\n| Helper | Purpose | Example |\r\n|--------|---------|---------|\r\n| `cd()` | Change directory | `cd('/tmp')` |\r\n| `fetch()` | HTTP requests, supports `.pipe()` | `fetch('https://api.example.com')` |\r\n| `question()` | Interactive user input | `question('Name: ')` |\r\n| `sleep()` | Delay execution | `await sleep(1000)` |\r\n| `echo()` | Print to stdout | `` echo`Status: ${p}` `` |\r\n| `stdin()` | Read stdin | `JSON.parse(await stdin())` |\r\n| `within()` | Isolated async config context | `within(() => { $.cwd = '/tmp' })` |\r\n| `retry()` | Retry with delay/backoff | `retry(5, () => $`curl url`)` |\r\n| `spinner()` | CLI progress indicator | `await spinner(() => $`long-cmd`)` |\r\n| `glob()` | File glob matching (globby) | `glob('**/*.js')` |\r\n| `which()` | Find executable path | `await which('node')` |\r\n| `ps` | Cross-platform process list | `ps.lookup({ command: 'node' })` |\r\n| `tmpdir()` | Temp directory | `tmpdir('sub')` |\r\n| `tmpfile()` | Temp file | `tmpfile('f.txt', 'content')` |\r\n| `argv` | Parsed CLI arguments | `argv.verbose` |\r\n| `dotenv` | .env file loading | `dotenv.config('.env')` |\r\n\r\n**Exposed npm packages:** `chalk` (colors), `fs` (fs-extra), `os`, `path`, `YAML` (yaml), `minimist`.\r\n\r\n## Resources\r\n\r\n- **[references/api.md](references/api.md)** — Full API reference: `$` options, `cd()`, `fetch()`, `question()`, `sleep()`, `echo()`, `stdin()`, `within()`, `retry()`, `spinner()`, `glob()`, `which()`, `ps`, `kill()`, `tmpdir()`, `tmpfile()`, `minimist`, `argv`, `chalk`, `fs`, `os`, `path`, `YAML`, `dotenv`, `quote()`, `useBash()`, `usePowerShell()`, `usePwsh()`\r\n- **[references/configuration.md](references/configuration.md)** — All `$.options`: `shell`, `prefix`, `postfix`, `quote`, `verbose`, `quiet`, `env`, `cwd`, `timeout`, `nothrow`, `detached`, `preferLocal`, `spawn`, `kill`, `log`, `input`, `signal`, `stdio`, `halt`, `delimiter`, `defaults`\r\n- **[references/cli.md](references/cli.md)** — CLI usage: flags, env vars, Markdown scripts, remote scripts, stdin execution, REPL mode\r\n- **[references/process.md](references/process.md)** — ProcessPromise/ProcessOutput lifecycle, piping, killing, aborting, output formatters, stream handling\r\n","topics":["Javascript","Typescript","Debugging"],"tags":{"latest":"1.0.0"},"stats":{"comments":0,"downloads":369,"installsAllTime":14,"installsCurrent":0,"stars":0,"versions":1},"createdAt":1777857936406,"updatedAt":1778492842520},"latestVersion":{"version":"1.0.0","createdAt":1777857936406,"changelog":"Initial release of zx skill — a guide for shell scripting using Google zx.\n\n- Provides comprehensive documentation and usage patterns for zx in JavaScript/TypeScript scripts.\n- Covers core concepts: the $ command, piping, ProcessPromise/ProcessOutput APIs, option presets, and CLI usage.\n- Includes quick start instructions and a decision tree for common scripting tasks.\n- Documents built-in helpers and best practices for writing, debugging, and refactoring zx scripts.\n- Clarifies the scope: for shell scripting with zx, not for unrelated Node.js questions.","license":"MIT-0"},"metadata":null,"owner":{"handle":"openlark","userId":"s1727wv2g20pc729snzcm4nf8183hy72","displayName":"OpenLark","image":"https://avatars.githubusercontent.com/u/260858787?v=4"},"moderation":null}