Skip to content

Commit

Permalink
fix: mixin ProcessOutput data to promisified pipe value (#954)
Browse files Browse the repository at this point in the history
continues #949
  • Loading branch information
antongolub authored Nov 24, 2024
1 parent 4bb470b commit 3b9afe1
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 37 deletions.
57 changes: 29 additions & 28 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
once,
parseDuration,
preferLocalBin,
proxyOverride,
quote,
quotePowerShell,
} from './util.js'
Expand Down Expand Up @@ -328,12 +329,12 @@ export class ProcessPromise extends Promise<ProcessOutput> {

// Essentials
pipe(dest: TemplateStringsArray, ...args: any[]): ProcessPromise
pipe<D extends Writable>(dest: D): D & PromiseLike<D>
pipe<D extends Writable>(dest: D): D & PromiseLike<ProcessOutput & D>
pipe<D extends ProcessPromise>(dest: D): D
pipe(
dest: Writable | ProcessPromise | TemplateStringsArray,
...args: any[]
): (Writable & PromiseLike<Writable>) | ProcessPromise {
): (Writable & PromiseLike<ProcessPromise & Writable>) | ProcessPromise {
if (isStringLiteral(dest, ...args))
return this.pipe($({ halt: true })(dest as TemplateStringsArray, ...args))
if (isString(dest))
Expand Down Expand Up @@ -372,7 +373,8 @@ export class ProcessPromise extends Promise<ProcessOutput> {
return dest
}
from.once('end', () => dest.emit('end-piped-from')).pipe(dest)
return promisifyStream(dest, this)
return promisifyStream(dest, this) as Writable &
PromiseLike<ProcessPromise & Writable>
}

abort(reason?: string) {
Expand Down Expand Up @@ -867,32 +869,31 @@ export function log(entry: LogEntry) {
}
}

export const promisifyStream = <S extends Writable>(
const promisifyStream = <S extends Writable>(
stream: S,
from?: ProcessPromise
): S & PromiseLike<S> =>
new Proxy(stream as S & PromiseLike<S>, {
get(target, key) {
if (key === 'run') return from?.run.bind(from)
if (key === 'then') {
return (res: any = noop, rej: any = noop) =>
new Promise((_res, _rej) =>
target
.once('error', (e) => _rej(rej(e)))
.once('finish', () => _res(res(target)))
.once('end-piped-from', () => _res(res(target)))
from: ProcessPromise
): S & PromiseLike<ProcessOutput & S> =>
proxyOverride(stream as S & PromiseLike<ProcessOutput & S>, {
then(res: any = noop, rej: any = noop) {
return new Promise((_res, _rej) =>
stream
.once('error', (e) => _rej(rej(e)))
.once('finish', () =>
_res(res(proxyOverride(stream, (from as any)._output)))
)
}
const value = Reflect.get(target, key)
if (key === 'pipe' && typeof value === 'function') {
return function (...args: any) {
const piped = value.apply(target, args)
piped._pipedFrom = from
return piped instanceof ProcessPromise
? piped
: promisifyStream(piped, from)
}
}
return value
.once('end-piped-from', () =>
_res(res(proxyOverride(stream, (from as any)._output)))
)
)
},
run() {
return from.run()
},
_pipedFrom: from,
pipe(...args: any) {
const piped = stream.pipe.apply(stream, args)
return piped instanceof ProcessPromise
? piped
: promisifyStream(piped as Writable, from)
},
})
13 changes: 13 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,3 +449,16 @@ export const once = <T extends (...args: any[]) => any>(fn: T) => {
return (result = fn(...args))
}
}

export const proxyOverride = <T extends object>(
origin: T,
...fallbacks: any
): T =>
new Proxy(origin, {
get(target: T, key) {
return (
fallbacks.find((f: any) => key in f)?.[key] ??
Reflect.get(target as T, key)
)
},
}) as T
6 changes: 3 additions & 3 deletions test-d/core.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ expectType<ProcessPromise>(p.nothrow())
expectType<ProcessPromise>(p.quiet())
expectType<ProcessPromise>(p.pipe($`cmd`))
expectType<ProcessPromise>(p.pipe`cmd`)
expectType<typeof process.stdout & PromiseLike<typeof process.stdout>>(
p.pipe(process.stdout)
)
expectType<
typeof process.stdout & PromiseLike<ProcessOutput & typeof process.stdout>
>(p.pipe(process.stdout))
expectType<ProcessPromise>(p.stdio('pipe'))
expectType<ProcessPromise>(p.timeout('1s'))
expectType<Promise<void>>(p.kill())
Expand Down
15 changes: 9 additions & 6 deletions test/core.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -475,13 +475,21 @@ describe('core', () => {
const p = $`echo "hello"`
.pipe(getUpperCaseTransform())
.pipe(fileStream)
const o = await p

assert.ok(p instanceof WriteStream)
assert.equal(await p, fileStream)
assert.ok(o instanceof WriteStream)
assert.equal(o.stdout, 'hello\n')
assert.equal(o.exitCode, 0)
assert.equal((await fs.readFile(file)).toString(), 'HELLO\n')
await fs.rm(file)
})

test('$ > stdout', async () => {
const p = $`echo 1`.pipe(process.stdout)
assert.deepEqual(p, process.stdout)
})

test('$ halted > stream', async () => {
const file = tempfile()
const fileStream = fs.createWriteStream(file)
Expand Down Expand Up @@ -514,11 +522,6 @@ describe('core', () => {

assert.equal(stdout, 'HELLO\n')
})

test('$ > stdout', async () => {
const p = $`echo 1`.pipe(process.stdout)
assert.equal(await p, process.stdout)
})
})

it('supports delayed piping', async () => {
Expand Down

0 comments on commit 3b9afe1

Please sign in to comment.