From bfff0efd78f284680c8e19e6c7022d28cc274d7a Mon Sep 17 00:00:00 2001 From: Anton Golub Date: Thu, 19 Sep 2024 13:19:10 +0300 Subject: [PATCH] fix: detect Duplex on pipe (#905) * fix: detect Duplex on pipe closes #904 * test: add isStringLiteral test --- src/core.ts | 5 ++--- src/util.ts | 14 ++++++++++++-- src/vendor-core.ts | 2 +- test/core.test.js | 7 +++++++ test/util.test.js | 12 ++++++++++++ 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/core.ts b/src/core.ts index 6289d702aa..3ad6aa4aa2 100644 --- a/src/core.ts +++ b/src/core.ts @@ -30,7 +30,6 @@ import { chalk, which, ps, - isStringLiteral, type ChalkInstance, type RequestInfo, type RequestInit, @@ -43,6 +42,7 @@ import { formatCmd, getCallerLocation, isString, + isStringLiteral, noop, parseDuration, preferLocalBin, @@ -325,7 +325,6 @@ export class ProcessPromise extends Promise { c ) => { self._resolved = true - // Ensures EOL if (stderr && !stderr.endsWith('\n')) c.on.stderr?.(eol, c) if (stdout && !stdout.endsWith('\n')) c.on.stdout?.(eol, c) @@ -466,7 +465,7 @@ export class ProcessPromise extends Promise { dest: Writable | ProcessPromise | TemplateStringsArray, ...args: any[] ): ProcessPromise { - if (isStringLiteral(dest)) + if (isStringLiteral(dest, ...args)) return this.pipe($(dest as TemplateStringsArray, ...args)) if (isString(dest)) throw new Error('The pipe() method does not take strings. Forgot $?') diff --git a/src/util.ts b/src/util.ts index c5262f2f10..e37be7eaab 100644 --- a/src/util.ts +++ b/src/util.ts @@ -17,8 +17,6 @@ import path from 'node:path' import fs from 'node:fs' import { chalk } from './vendor-core.js' -export { isStringLiteral } from './vendor-core.js' - export function tempdir(prefix = `zx-${randomId()}`) { const dirpath = path.join(os.tmpdir(), prefix) fs.mkdirSync(dirpath, { recursive: true }) @@ -47,6 +45,18 @@ export function isString(obj: any) { return typeof obj === 'string' } +export const isStringLiteral = ( + pieces: any, + ...rest: any[] +): pieces is TemplateStringsArray => { + return ( + pieces?.length > 0 && + pieces.raw?.length === pieces.length && + Object.isFrozen(pieces) && + rest.length + 1 === pieces.length + ) +} + const pad = (v: string) => (v === ' ' ? ' ' : '') export function preferLocalBin( diff --git a/src/vendor-core.ts b/src/vendor-core.ts index b1b19571cc..5376b5e6c9 100644 --- a/src/vendor-core.ts +++ b/src/vendor-core.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -export { exec, buildCmd, isStringLiteral, type TSpawnStore } from 'zurk/spawn' +export { exec, buildCmd, type TSpawnStore } from 'zurk/spawn' export type RequestInfo = Parameters[0] export type RequestInit = Parameters[1] diff --git a/test/core.test.js b/test/core.test.js index cf234f5549..b033143eaf 100644 --- a/test/core.test.js +++ b/test/core.test.js @@ -358,6 +358,13 @@ describe('core', () => { assert.equal(p.stdout.trim(), 'foo') }) + test('accepts stdout', async () => { + const p1 = $`echo pipe-to-stdout` + const p2 = p1.pipe(process.stdout) + assert.equal(p1, p2) + assert.equal((await p1).stdout.trim(), 'pipe-to-stdout') + }) + test('checks argument type', async () => { let err try { diff --git a/test/util.test.js b/test/util.test.js index 3770b972ea..1176447634 100644 --- a/test/util.test.js +++ b/test/util.test.js @@ -20,6 +20,7 @@ import { errnoMessage, formatCmd, isString, + isStringLiteral, noop, parseDuration, quote, @@ -59,6 +60,17 @@ describe('util', () => { assert.ok(!isString(1)) }) + test('isStringLiteral()', () => { + const bar = 'baz' + assert.ok(isStringLiteral``) + assert.ok(isStringLiteral`foo`) + assert.ok(isStringLiteral`foo ${bar}`) + + assert.ok(!isStringLiteral('')) + assert.ok(!isStringLiteral('foo')) + assert.ok(!isStringLiteral(['foo'])) + }) + test('quote()', () => { assert.ok(quote('string') === 'string') assert.ok(quote(`'\f\n\r\t\v\0`) === `$'\\'\\f\\n\\r\\t\\v\\0'`)