Skip to content

Conversation

@renovate
Copy link
Contributor

@renovate renovate bot commented Nov 21, 2025

This PR contains the following updates:

Package Change Age Adoption Passing Confidence
zx (source) 8.3.0 -> 8.8.5 age adoption passing confidence

GitHub Vulnerability Alerts

CVE-2025-13437

When zx is invoked with --prefer-local=, the CLI creates a symlink named ./node_modules pointing to /node_modules. Due to a logic error in src/cli.ts (linkNodeModules / cleanup), the function returns the target path instead of the alias (symlink path). The later cleanup routine removes what it received, which deletes the target directory itself. Result: zx can delete an external /node_modules outside the current working directory.


Release Notes

google/zx (zx)

v8.8.5: — Temporary Reservoir

Compare Source

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

Also [email protected] arrives here.

v8.8.4: — Flange Coupling

Compare Source

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

v8.8.3: — Sealing Gasket

Compare Source

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

v8.8.2: — Leaking Valve

Compare Source

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

v8.8.1: — Turbo Flush

Compare Source

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

v8.8.0: — Pressure Tested

Compare Source

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

v8.7.2: — Copper Crafter

Compare Source

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'
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
}

v8.7.1: — Pipe Whisperer

Compare Source

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

v8.7.0: — Solder Savior

Compare Source

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)

v8.6.2: — Flow Unstoppable

Compare Source

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

v8.6.1: — Drain Hero

Compare Source

  • Use process.env.SHELL as default shell if defined #​1252
SHELL=/bin/zsh zx script.js
  • Accept numeric strings as parseDuration() arg #​1249
await sleep(1000)   // 1 second
await sleep('1000') // 1 second

v8.6.0: — Valve Vanguard

Compare Source

  • Enabled thenable params processing for $ literals #​1237
const a1 = $`echo foo`
const a2 = new Promise((resolve) => setTimeout(resolve, 20, ['bar', 'baz']))

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

v8.5.5: — PVC Wizard

Compare Source

Minor feature polish.

  • ProcessPromise and ProcessOutput lines() getters now accept a custom delimiter #​1220 #​1218
const cwd = tempdir()
const delimiter = '\0'

const p1 = $({
  cwd
})`touch foo bar baz; find ./ -type f -print0 -maxdepth 1`
(await p1.lines(delimiter)).sort() // ['./bar', './baz', './foo']
  
// or via options
const lines = []
const p2 = $({
  delimiter,
  cwd,
})`find ./ -type f -print0 -maxdepth 1`

for await (const line of p2) {
  lines.push(line)
}

lines.sort() // ['./bar', './baz', './foo']

v8.5.4: — Pipe Dreamer

Compare Source

v8.5.3: — Trap Master

Compare Source

  • Another portion of JSR related improvements #​1193 #​1192
  • Goods refactoring #​1195
    • Fixes expBackoff implementation
    • Sets $.log.output as default spinner() output
    • Makes configurable question() I/O
  • Added Graaljs compatability test #​1194
  • Docs improvements, usage examples updates #​1198

v8.5.2: — Threaded Perfection

Compare Source

v8.5.0: — Flow Splitter

Compare Source

In this release we're significantly expanding the zx setup capabilities.

zx@​lite

Just core functions without extras, ~7x smaller than the full version.
#​1131

npm i zx@lite
npm i [email protected]

Detailed comparison: zx/versions

import { $ } from 'zx'
await $`echo foo`
Channels

We have completely reforged the distribution flow. Now zx is available in multiple formats:

##### npm pkg from registry.npmjs.org
npm i zx        

##### install directly from the GH
npm i google/zx 

##### from GH the npm registry
npm i --registry=https://npm.pkg.github.com @&#8203;google/zx

##### fetch from the JSR
##### https://jsr.io/docs/using-packages

##### @&#8203;webpod is temporary JSR scope until @&#8203;google/zx becomes ready, we'll migrate later
npx jsr add @&#8203;webpod/zx
deno add jsr:@&#8203;webpod/zx

##### homebrew formula
##### https://github.com/Homebrew/homebrew-core/blob/master/Formula/z/zx.rb
brew install zx

#​1141...
#​1157

Docker

If you'd prefer to run zx in a container, you can pull the image from the GH docker registry.
node:22-alpine is used as a base. #​1142 #​1145

docker pull ghcr.io/google/zx:8.5.0
docker run -t ghcr.io/google/zx:8.5.0 -e="await \$({verbose: true})\`echo foo\`"
docker run -t -i -v ./:/script ghcr.io/google/zx:8.5.0 script/t.js
Chores
  • Introduced fetch pipe helper to bypass string size limits #​1130 #​977
const p1 = fetch('https://example.com').pipe($`cat`)
const p2 = fetch('https://example.com').pipe`cat`
import { glob } from 'zx'
const packages = glob.sync(['package.json', 'packages/*/package.json'])
ZX_REGISTRY='https://custom-registry.example.com' zx script.js

v8.4.2

Compare Source

v8.4.1: – Rusty Elbow

Compare Source

Logger enhancements are arriving in this release. #​1119 #​1122 #​1123 #​1125

  • You can customize the output by defining your own formatters for each log entry kind.
$.log.formatters = {
  cmd: (entry: LogEntry) => `CMD: ${entry.cmd}`,
  fetch: (entry: LogEntry) => `FETCH: ${entry.url}`
  //...
}
  • Cmd highlighter now should properly detect bins and arguments. If still not, please report it in #​1122
  • Switched to TS 5.8 #​1120
  • Applied zizmor to check GHA workflows #​1126
  • Prettier is now enabled as a pre-commit hook #​1118

v8.4.0: – Drip Detective

Compare Source

Try the new batch of enhancements: npm i [email protected] https://www.npmjs.com/package/zx/v/8.4.0

Changes
  • The CLI option --prefer-local now allows linking both external binaries and packages #​1116 #​1117
const cwd = tmpdir()
const external = tmpdir()
await fs.outputJson(path.join(external, 'node_modules/a/package.json'), {
  name: 'a',
  version: '1.0.0',
  type: 'module',
  exports: './index.js',
})
await fs.outputFile(
  path.join(external, 'node_modules/a/index.js'),
  `
export const a = 'AAA'
`
)
const script = `
import {a} from 'a'
console.log(a);
`
const out = await $`zx --cwd=${cwd} --prefer-local=${external} --test <<< ${script}`
assert.equal(out.stdout, 'AAA\n')
  • The quote has been slightly changed for a conner case, when zx literal gets an array.
    #​999 #​1113
const p = $({prefix: '', postfix: ''})`echo ${[1, '', '*', '2']}`

// before
p.cmd //  `echo 1  $'*' 2`) 

// after
p.cmd //  `echo 1 $'' $'*' 2`) 
zx script.zx           # Unknown file extension "\.zx"
zx --ext=mjs script.zx # OK
const err = new Error('BrokenSpawn')
const o = await $({
  nothrow: true,
  spawn() {
    throw err
  },
})`echo foo`
o.ok       // false
o.exitCode // null
o.message  // BrokenSpawn...
o.cause    // err

v8.3.2: – Clogged Drain

Compare Source

Restrics unsafe vals usage on dotenv.stringify #​1093 #​1094

v8.3.1: – Perfect Seal

Compare Source

The release essence: introduced full-featured .env support #​461#​1060 #​1052 #​1043 #​1037 #​1032 #​1030 #​1022

API

envapi is a tiny 100 LOC dotenv-inspired parser and serializer that we've integrated into zx.

import { dotenv, fs } from 'zx'

// parse
const env = dotenv.parse('A=A\nFOO=BAR') // { A: 'A', FOO: 'BAR' }

// serialize
const raw = dotenv.stringify(env) // A=A\nFOO=BAR
await fs.writeFile('.env', raw)

// load
dotenv.load('.env') // { A: 'A', FOO: 'BAR' }

// update the process.env
dotenv.config('.env')
process.env.A // A
CLI
zx --env .env script.mjs
zx --env-file .env script.mjs
QnA

— Why not use dotenv directly?
— 1) Size does matter 2) We'd like to avoid internal vaults.

— Why not load .env by default?
— 1) Explicit is better than implicit 2) Runtime itself (like bun) may provide the feature.

Chore

Configuration

📅 Schedule: Branch creation - "" in timezone America/Los_Angeles, Automerge - At any time (no schedule defined).

🚦 Automerge: Enabled.

Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@changeset-bot
Copy link

changeset-bot bot commented Nov 21, 2025

⚠️ No Changeset found

Latest commit: b423300

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 21, 2025

npm i https://pkg.pr.new/apollographql/apollo-client/@apollo/client@13029

commit: b423300

@apollo-librarian
Copy link

apollo-librarian bot commented Nov 21, 2025

✅ Docs preview has no changes

The preview was not built because there were no changes.

Build ID: c75fb0e1699478705071740c
Build Logs: View logs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant