diff --git a/src/core.ts b/src/core.ts index ea5048cfad..6a5693a1a9 100644 --- a/src/core.ts +++ b/src/core.ts @@ -115,17 +115,21 @@ export const defaults: Options = { timeoutSignal: 'SIGTERM', } -export interface Shell { - (pieces: TemplateStringsArray, ...args: any[]): ProcessPromise - (opts: Partial): Shell +// prettier-ignore +export interface Shell< + S = false, + R = S extends true ? ProcessOutput : ProcessPromise, +> { + (pieces: TemplateStringsArray, ...args: any[]): R + = Partial, R = O extends { sync: true } ? Shell : Shell>(opts: O): R sync: { (pieces: TemplateStringsArray, ...args: any[]): ProcessOutput - (opts: Partial): Shell + (opts: Partial>): Shell } } export const $: Shell & Options = new Proxy( - function (pieces, ...args) { + function (pieces: TemplateStringsArray | Partial, ...args: any) { const snapshot = getStore() if (!Array.isArray(pieces)) { return function (this: any, ...args: any) { @@ -136,9 +140,7 @@ export const $: Shell & Options = new Proxy( } } const from = getCallerLocation() - if (pieces.some((p) => p == undefined)) { - throw new Error(`Malformed command at ${from}`) - } + checkCmd(pieces as TemplateStringsArray, from) checkShell() checkQuote() @@ -719,6 +721,12 @@ function checkQuote() { throw new Error('No quote function is defined: https://ï.at/no-quote-func') } +function checkCmd(pieces: TemplateStringsArray, from: string) { + if (pieces.some((p) => p == undefined)) { + throw new Error(`Malformed command at ${from}`) + } +} + let cwdSyncHook: AsyncHook export function syncProcessCwd(flag: boolean = true) { diff --git a/test-d/core.test-d.ts b/test-d/core.test-d.ts index 6cd9b32242..9496f5d669 100644 --- a/test-d/core.test-d.ts +++ b/test-d/core.test-d.ts @@ -56,3 +56,8 @@ expectType(new ProcessOutput(null, null, '', '', '', '')) expectError(new ProcessOutput(null, null)) expectType<'banana'>(within(() => 'apple' as 'banana')) + +expectType($`cmd`) +expectType($({ sync: false })`cmd`) +expectType($({ sync: true })`cmd`) +expectType($.sync`cmd`) diff --git a/test/smoke/ts.test.ts b/test/smoke/ts.test.ts index 37044a29c5..b3d76b5a33 100644 --- a/test/smoke/ts.test.ts +++ b/test/smoke/ts.test.ts @@ -15,12 +15,18 @@ import * as assert from 'node:assert' import 'zx/globals' ;(async () => { - // smoke test + // smoke test async { const p = await $`echo foo` assert.match(p.stdout, /foo/) } + // smoke test sync + { + const p = $.sync`echo foo` + assert.match(p.stdout, /foo/) + } + // captures err stack { const p = await $({ nothrow: true })`echo foo; exit 3`