Skip to content

Commit 8900e45

Browse files
authored
fix: autorun halted processes on pipe run (google#951)
relates google#949
1 parent 15bb135 commit 8900e45

File tree

3 files changed

+51
-11
lines changed

3 files changed

+51
-11
lines changed

.size-limit.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{
33
"name": "zx/core",
44
"path": ["build/core.cjs", "build/util.cjs", "build/vendor-core.cjs"],
5-
"limit": "71.1 kB",
5+
"limit": "72 kB",
66
"brotli": false,
77
"gzip": false
88
},
@@ -30,7 +30,7 @@
3030
{
3131
"name": "all",
3232
"path": "build/*",
33-
"limit": "833.6 kB",
33+
"limit": "835 kB",
3434
"brotli": false,
3535
"gzip": false
3636
}

src/core.ts

+23-9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { type AsyncHook, AsyncLocalStorage, createHook } from 'node:async_hooks'
2323
import { type Readable, type Writable } from 'node:stream'
2424
import { inspect } from 'node:util'
2525
import { EOL as _EOL } from 'node:os'
26+
import { EventEmitter } from 'node:events'
2627
import {
2728
exec,
2829
buildCmd,
@@ -205,6 +206,10 @@ export class ProcessPromise extends Promise<ProcessOutput> {
205206
private _resolved = false
206207
private _halted?: boolean
207208
private _piped = false
209+
private _pipedFrom?: ProcessPromise
210+
private _run = false
211+
private _ee = new EventEmitter()
212+
private _stdin = new VoidStream()
208213
private _zurk: ReturnType<typeof exec> | null = null
209214
private _output: ProcessOutput | null = null
210215
private _reject: Resolve = noop
@@ -225,7 +230,10 @@ export class ProcessPromise extends Promise<ProcessOutput> {
225230
}
226231

227232
run(): ProcessPromise {
228-
if (this.child) return this // The _run() can be called from a few places.
233+
if (this._run) return this // The _run() can be called from a few places.
234+
this._halted = false
235+
this._run = true
236+
this._pipedFrom?.run()
229237

230238
const $ = this._snapshot
231239
const self = this
@@ -255,9 +263,11 @@ export class ProcessPromise extends Promise<ProcessOutput> {
255263
spawn: $.spawn,
256264
spawnSync: $.spawnSync,
257265
store: $.store,
266+
stdin: self._stdin,
258267
stdio: self._stdio ?? $.stdio,
259268
sync: $[SYNC],
260269
detached: $.detached,
270+
ee: self._ee,
261271
run: (cb) => cb(),
262272
on: {
263273
start: () => {
@@ -326,20 +336,18 @@ export class ProcessPromise extends Promise<ProcessOutput> {
326336
...args: any[]
327337
): (Writable & PromiseLike<Writable>) | ProcessPromise {
328338
if (isStringLiteral(dest, ...args))
329-
return this.pipe($(dest as TemplateStringsArray, ...args))
339+
return this.pipe($({ halt: true })(dest as TemplateStringsArray, ...args))
330340
if (isString(dest))
331341
throw new Error('The pipe() method does not take strings. Forgot $?')
332342

333343
this._piped = true
334-
const { store, ee, fulfilled } = this._zurk!
344+
const ee = this._ee
335345
const from = new VoidStream()
336346
const fill = () => {
337-
for (const chunk of store.stdout) {
338-
from.write(chunk)
339-
}
347+
for (const chunk of this._zurk!.store.stdout) from.write(chunk)
340348
}
341349

342-
if (fulfilled) {
350+
if (this._resolved) {
343351
fill()
344352
from.end()
345353
} else {
@@ -354,8 +362,14 @@ export class ProcessPromise extends Promise<ProcessOutput> {
354362
}
355363

356364
if (dest instanceof ProcessPromise) {
357-
this.catch((e) => (dest.isNothrow() ? noop : dest._reject(e)))
358-
from.pipe(dest.stdin)
365+
dest._pipedFrom = this
366+
367+
if (dest.isHalted() && this.isHalted()) {
368+
ee.once('start', () => from.pipe(dest.run()._stdin))
369+
} else {
370+
this.catch((e) => (dest.isNothrow() ? noop : dest._reject(e)))
371+
from.pipe(dest.run()._stdin)
372+
}
359373
return dest
360374
}
361375
from.once('end', () => dest.emit('end-piped-from')).pipe(dest)

test/core.test.js

+26
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,32 @@ describe('core', () => {
436436
assert.equal(o2, 'HELLO WORLD\n')
437437
})
438438

439+
test('$ > $ halted', async () => {
440+
const $h = $({ halt: true })
441+
const { stdout } = await $`echo "hello"`
442+
.pipe($h`awk '{print $1" world"}'`)
443+
.pipe($h`tr '[a-z]' '[A-Z]'`)
444+
445+
assert.equal(stdout, 'HELLO WORLD\n')
446+
})
447+
448+
test('$ halted > $ halted', async () => {
449+
const $h = $({ halt: true })
450+
const { stdout } = await $h`echo "hello"`
451+
.pipe($h`awk '{print $1" world"}'`)
452+
.pipe($h`tr '[a-z]' '[A-Z]'`)
453+
.run()
454+
455+
assert.equal(stdout, 'HELLO WORLD\n')
456+
})
457+
458+
test('$ halted > $ literal', async () => {
459+
const { stdout } = await $({ halt: true })`echo "hello"`
460+
.pipe`awk '{print $1" world"}'`.pipe`tr '[a-z]' '[A-Z]'`.run()
461+
462+
assert.equal(stdout, 'HELLO WORLD\n')
463+
})
464+
439465
test('$ > stream', async () => {
440466
const file = tempfile()
441467
const fileStream = fs.createWriteStream(file)

0 commit comments

Comments
 (0)