Skip to content

Releases: google/zx

8.8.5 — Temporary Reservoir

19 Oct 13:47
1ca9270

Choose a tag to compare

This release fixes the issue, when zx flushes external node_modules on linking #1348 #1349 #1355

Also globby@15.0.0 arrives here.

8.8.4 — Flange Coupling

26 Sep 20:56
bfa8e6a

Choose a tag to compare

It's time. This release updates zx internals to make the ps API and related methods ProcessPromise.kill(), kill() work on Windows systems without wmic.
#1344 webpod/ps#15

  1. WMIC will be missing in Windows 11 25H2 (kernel >= 26000)
  2. The windows-latest label in GitHub Actions will migrate from Windows Server 2022 to Windows Server 2025 beginning September 2, 2025 and finishing by September 30, 2025.

https://github.blog/changelog/2025-07-31-github-actions-new-apis-and-windows-latest-migration-notice/#windows-latest-image-label-migration

8.8.3 — Sealing Gasket

20 Sep 13:25
f1b9ed7

Choose a tag to compare

Continues #1339 to prevent injections via Proxy input or custom toString() manipulations.

8.8.2 — Leaking Valve

19 Sep 21:09
6d611de

Choose a tag to compare

Fixes potential cmd injection via kill() method for Windows platform. #1337 #1339. Affects the versions range 8.7.1...8.8.1.

8.8.1 — Turbo Flush

20 Aug 16:22
67743df

Choose a tag to compare

We keep improving the projects internal infra to bring more stability, safety and performance for artifacts.

Featfixes

  • Applied flags filtration for CLI-driven deps install #1308
  • Added kill() event logging #1312
  • Set SIGTERM as kill() fallback signal #1313
  • Allowed stdio() arg be an array #1311
const p = $({halt: true})`cmd`
p.stdio([stream, 'ignore', 'pipe'])

Enhancements

8.8.0 — Pressure Tested

05 Aug 14:01
820a85c

Choose a tag to compare

This release enhances the coherence between the ProcessPromise and the Streams API, eliminating the need for certain script-level workarounds.

✨ New Features

unpipe() — Selectively stop piping

You can now call .unpipe() to stop data transfer from a source to a destination without closing any of the pair. #1302

const p1 = $`echo foo && sleep 0.1 && echo bar && sleep 0.1 && echo baz && sleep 0.1 && echo qux`
const p2 = $`echo 1 && sleep 0.15 && echo 2 && sleep 0.1 && echo 3`
const p3 = $`cat`

p1.pipe(p3)
p2.pipe(p3)

setTimeout(() => p1.unpipe(p3), 150)

const { stdout } = await p3
// 'foo\n1\nbar\n2\n3\n'

Many-to-one piping

Multiple sources can now stream into a single destination. All sources complete before the destination closes. #1300

const $h = $({ halt: true })
const p1 = $`echo foo`
const p2 = $h`echo a && sleep 0.1 && echo c && sleep 0.2 && echo e`
const p3 = $h`sleep 0.05 && echo b && sleep 0.1 && echo d`
const p4 = $`sleep 0.4 && echo bar`
const p5 = $h`cat`

await p1
p1.pipe(p5)
p2.pipe(p5)
p3.pipe(p5)
p4.pipe(p5)

const { stdout } = await p5.run()
// 'foo\na\nb\nc\nd\ne\nbar\n'

Piping from rejected processes

Processes that exit with errors can now still pipe their output. The internal recorder retains their stream, status, and exit code. #1296

const p1 = $({ nothrow: true })`echo foo && exit 1`
await p1

const p2 = p1.pipe($({ nothrow: true })`cat`)
await p2

p1.output.toString() // 'foo\n'
p1.output.ok         // false
p1.output.exitCode   // 1

p2.output.toString() // 'foo\n'
p2.output.ok         // false
p2.output.exitCode   // 1

Components versions

Since zx bundles third-party libraries without their package.jsons, their versions weren’t previously visible. You can now access them via the versions static map — including zx itself. #1298 #1295

import { versions } from 'zx'

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

8.7.2 — Copper Crafter

29 Jul 12:40
b1a996a

Choose a tag to compare

Stability and customizability improvements

  • Handle nothrow option on ProcessPromise init stage #1288
const o = await $({ nothrow: true })`\033`
o.ok      // false
o.cause   // Error
  • Handle _snapshot.killSignal value on kill() #1287
const p = $({killSignal: 'SIGKILL'})`sleep 10`
await p.kill()
p.signal  // 'SIGKILL'
  • Introduced Fail class #1285
import { Fail } from 'zx'

Fail.EXIT_CODES['2'] = 'Custom error message'
Fail.formatErrorMessage = (err: Error, from: string): string =>
  `${err.message} (${from})`
import type { $, Options } from 'zx'

const custom$: $ = (pieces: TemplateStringsArray | Partial<Options>, ...args: any[]) => {
  // ... custom implementation
}
  • Internal tweak ups #1276 #1277 #1278 #1279 #1280 #1281 #1282 #1286 #1289

  • Described the zx architecture basics. This section helps to better understand the zx concepts and internal logic, and will be useful for those who want to become a project contributor, make tools based on it, or create something similar from scratch. #1290 #1291 #1292

8.7.1 — Pipe Whisperer

14 Jul 14:30
afcb0eb

Choose a tag to compare

Continues v8.7.0: handles new ps() corner case and improves $.kill mechanics on Windows #1266 #1267 #1269 webpod/ps#14

8.7.0 — Solder Savior

11 Jul 20:12
978c0e1

Choose a tag to compare

Important fixes for annoying flaky bugs

kill() 🐞

We've found an interesting case #1262

const p = $`sleep 1000`
const {pid} = p // 12345
await p.kill()

If we kill the process again, the result might be unexpected:

await ps({pid}) // {pid: 12345, ppid: 67890, command: 'another command', ...}
p.kill()

This happens because the pid may be reused by the system for another process, so we've added extra assertions to prevent indeterminacy:

p.kill()  // Error: Too late to kill the process.
p.abort() // Error: Too late to abort the process.

ps() 🐛

  • ps() uses wmic internally on Windows, it relies on fragile heuristics to parse the output. We have improved this logic to handle more format variants, but over time (in v9 maybe) we're planning to change the approach.

#1256 #1263 webpod/ps#12 webpod/ingrid#6

const [root] = await ps.lookup({ pid: process.pid })
assert.equal(root.pid, process.pid)

8.6.2 — Flow Unstoppable

09 Jul 14:10
523ef8e

Choose a tag to compare

Fixes $.prefix & $.postfix values settings via env variables #1261 #1260