Releases: google/zx
8.3.0 – Pipes of Steel
A few weeks ago zx took a part in OSS Library Night 🎉
Many thanks to the organizers and contributors who have boosted the project with their pull requests!
Today we are releasing the zx with a huge bunch of new features and improvements.
Features
API
- Implemented
[Symbol.asyncIterator]
API forProcessPromise
#984 #998 #1000
Now you can iterate over the process output usingfor await
loop from any point of the process execution.
const process = $`sleep 0.1; echo Chunk1; sleep 0.1; echo Chunk2; sleep 0.2; echo Chunk3; sleep 0.1; echo Chunk4;`
const chunks = []
await new Promise((resolve) => setTimeout(resolve, 250))
for await (const chunk of process) {
chunks.push(chunk)
}
chunks.length // 4
chunks[0] // 'Chunk1'
chunks[3] // 'Chunk4'
- zx version is available via JS API #986
import { version } from 'zx'
const [major] = (version || '').split('.').map(Number)
if (major < 6)
throw new Error('zx >= 6 is required')
Pipes
- Enabled stream picking for
pipe()
#1023
const p = $`echo foo >&2; echo bar`
const o1 = (await p.pipe.stderr`cat`).toString()
const o2 = (await p.pipe.stdout`cat`).toString()
assert.equal(o1, 'foo\n') // <- piped from stderr
assert.equal(o2, 'bar\n') // <- stdout
- Added
signal
handling on piping #992
const ac = new AbortController()
const { signal } = ac
const p = $({ signal, nothrow: true })`echo test`.pipe`sleep 999`
setTimeout(() => ac.abort(), 50)
try {
await p
} catch ({ message }) {
message // The operation was aborted
}
- Added direct piping to file shortcut #1001
// before
await $`echo "Hello, stdout!"`.pipe(fs.createWriteStream('/tmp/output.txt'))
// after
await $`echo "Hello, stdout!"`.pipe('/tmp/output.txt')
CLI
ZX_VERBOSE=true ZX_SHELL='/bin/bash' zx script.mjs
zx --env=/path/to/some.env script
- Landed installation registry customization #994
zx --install --registry=https://registry.yarnpkg.com script.mjs
- Added
--prefer-local
option #1015
Fixes
- Fixed temp assets clutter on
process.exit()
#993 #997 - Handle tslib-generated string templates #966
- Disabled spinner on CI and in quiet mode #1008 #1009 #1017
- Added missing
ZX_SHELL
env handling #1024
Docs
- Contribution guide updated #983
- Documentation is now built from the
main
branch #985 - Finally synced with the current API #1025 #1026
Chores
- Added autotest generation for 3rd party libs export #987 #990 #1007 #1021
- Added some jsr.io pre-publish tests #989 #991
- Optimized package.json on publishing #1005 #1006
- Attached
.node_version
to improve contributors devx #1012 - Built-in
chalk
updated to v5.4.1 #1019
Merry Christmas! 🎄🎅🎁
8.2.4 – Leaky Faucet
- Fixed bun async_hooks compatibility #959
8.2.3 – Golden Wrench
This release continues the work on pipe API enhancements:
const { stdout } = await $({ halt: true })`echo "hello"`
.pipe`awk '{print $1" world"}'`
.pipe`tr '[a-z]' '[A-Z]'`
.run()
stdout // 'HELLO WORLD'
- Let $ be piped directly from streams #953
const getUpperCaseTransform = () =>
new Transform({
transform(chunk, encoding, callback) {
callback(null, String(chunk).toUpperCase())
},
})
// $ > stream (promisified) > $
const o1 = await $`echo "hello"`
.pipe(getUpperCaseTransform())
.pipe($`cat`)
o1.stdout // 'HELLO\n'
// stream > $
const file = tempfile()
await fs.writeFile(file, 'test')
const o2 = await fs
.createReadStream(file)
.pipe(getUpperCaseTransform())
.pipe($`cat`)
o2.stdout // 'TEST'
const file = tempfile()
const fileStream = fs.createWriteStream(file)
const p = $`echo "hello"`
.pipe(getUpperCaseTransform())
.pipe(fileStream)
const o = await p
p instanceof WriteStream // true
o instanceof WriteStream // true
o.stdout // 'hello\n'
o.exitCode; // 0
(await fs.readFile(file)).toString() // 'HELLO\n'
We've also slightly tweaked up dist contents for better compatibility with bundlers #957
8.2.2
What's Changed
- test: stdio inherit by @antongolub in #941
- fix: handle nullable stdout/stderr by @antongolub in #943
Full Changelog: 8.2.1...8.2.2
8.2.1
8.2.0
Pipes supercharge today! 🚀
Features
- Delayed piping. You can fill dependent streams at any time during the origin process lifecycle (even when finished) without losing data. #914
const result = $`echo 1; sleep 1; echo 2; sleep 1; echo 3`
const piped1 = result.pipe`cat`
let piped2
setTimeout(() => {
piped2 = result.pipe`cat`
}, 1500)
await piped1
assert.equal((await piped1).toString(), '1\n2\n3\n')
assert.equal((await piped2).toString(), '1\n2\n3\n')
const file = tempfile()
const fileStream = fs.createWriteStream(file)
const p = $`echo "hello"`
.pipe(
new Transform({
transform(chunk, encoding, callback) {
callback(null, String(chunk).toUpperCase())
},
})
)
.pipe(fileStream)
p instanceof WriteStream // true
await p === fileStream // true
(await fs.readFile(file)).toString() // 'HELLO\n'
Chore
8.1.9
Today's release is a minor update that includes:
Enhancements
- We have replaced
ProcessOutput
fields with lazy getters to reduce mem consumption #903, #908 - Reduced ReDos risks for codeblock patterns #906
- Kept
argv
reference on update #916 - Strengthened
Shell
interface to properly handlesync
mode #915:
expectType<ProcessPromise>($`cmd`)
expectType<ProcessPromise>($({ sync: false })`cmd`)
expectType<ProcessOutput>($({ sync: true })`cmd`)
expectType<ProcessOutput>($.sync`cmd`)
Fixes
8.1.8
8.1.7
Step by step on the road to improvements
Fixes
Finally, we've fixed the issue with piped process rejection #640 #899:
const p1 = $`exit 1`.pipe($`echo hello`)
try {
await p1
} catch (e) {
assert.equal(e.exitCode, 1)
}
const p2 = await $({ nothrow: true })`echo hello && exit 1`.pipe($`cat`)
assert.equal(p2.exitCode, 0)
assert.equal(p2.stdout.trim(), 'hello')
Enhancements
Added cmd
display to ProcessPromise
#891:
const foo = 'bar'
const p = $`echo ${foo}`
p.cmd // 'echo bar'
and duration
field to ProcessOutput
#892:
const p = $`sleep 1`.nothrow()
const o = await p
o.duration // ~1000 (in ms)
Enabled zurk-like pipe string literals #900:
const p = await $`echo foo`.pipe`cat`
p.stdout.trim() // 'foo'
8.1.6
Improvements & Fixes
$.preferLocal = true // injects node_modules/.bin to the $PATH
$.preferLocal = '/foo/bar' // attaches /foo/bar to the $PATH
$.preferLocal = ['/bar', '/baz'] // now the $PATH includes both /bar and /baz
Why not just $.env['PATH'] = 'extra:' + '$.env['PATH']
?
Well, the API internally does the same, but also handles the win paths peculiarities.
- Provided
$.killSignal
option for the symmetry with the$.timeoutSignal
. You can override the default termination flow #885:
$.killSignal = 'SIGKILL'
const p = $({nothrow: true})`sleep 10000`
setTimeout(p.kill, 100)
(await p).signal // SIGKILL
$
opt presets became chainable #883:
const $$ = $({ nothrow: true })
assert.equal((await $$`exit 1`).exitCode, 1)
const $$$ = $$({ sync: true }) // Both {nothrow: true, sync: true} are applied
assert.equal($$$`exit 2`.exitCode, 2)
- Enhanced the internal
Duration
parser #884:
const p = $({timeout: '1mss'})`sleep 999` // raises an error now