diff --git a/src/core.ts b/src/core.ts index aea3be1b58..30f108cd52 100644 --- a/src/core.ts +++ b/src/core.ts @@ -14,7 +14,7 @@ import assert from 'node:assert' import { spawn, spawnSync, StdioNull, StdioPipe } from 'node:child_process' -import { AsyncLocalStorage, createHook } from 'node:async_hooks' +import { AsyncHook, AsyncLocalStorage, createHook } from 'node:async_hooks' import { Readable, Writable } from 'node:stream' import { inspect } from 'node:util' import { @@ -38,6 +38,7 @@ import { parseDuration, quote, quotePowerShell, + noquote, } from './util.js' export interface Shell { @@ -74,14 +75,18 @@ export interface Options { } const storage = new AsyncLocalStorage() -const hook = createHook({ +const cwdSyncHook: AsyncHook & { enabled?: boolean } = createHook({ init: syncCwd, before: syncCwd, promiseResolve: syncCwd, after: syncCwd, destroy: syncCwd, }) -hook.enable() + +export function syncProcessCwd(flag: boolean = true) { + if (flag) cwdSyncHook.enable() + else cwdSyncHook.disable() +} export const defaults: Options = { [processCwd]: process.cwd(), @@ -94,9 +99,7 @@ export const defaults: Options = { quiet: false, prefix: '', postfix: '', - quote: () => { - throw new Error('No quote function is defined: https://ï.at/no-quote-func') - }, + quote: noquote, spawn, spawnSync, log, @@ -104,14 +107,14 @@ export const defaults: Options = { } const isWin = process.platform == 'win32' -export function setupPowerShell() { +export function usePowerShell() { $.shell = which.sync('powershell.exe') $.prefix = '' $.postfix = '; exit $LastExitCode' $.quote = quotePowerShell } -export function setupBash() { +export function useBash() { $.shell = which.sync('bash') $.prefix = 'set -euo pipefail;' $.quote = quote @@ -184,9 +187,8 @@ export const $: Shell & Options = new Proxy( }, } ) - try { - setupBash() + useBash() } catch (err) {} type Resolve = (out: ProcessOutput) => void diff --git a/src/globals.ts b/src/globals.ts index 68ffc835bc..5c85d97755 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -41,7 +41,8 @@ declare global { var quote: typeof _.quote var quotePowerShell: typeof _.quotePowerShell var retry: typeof _.retry - var setupPowerShell: typeof _.setupPowerShell + var usePowerShell: typeof _.usePowerShell + var useBash: typeof _.useBash var sleep: typeof _.sleep var spinner: typeof _.spinner var stdin: typeof _.stdin diff --git a/src/util.ts b/src/util.ts index e152e87c7a..8fcd79a753 100644 --- a/src/util.ts +++ b/src/util.ts @@ -42,6 +42,10 @@ export function normalizeMultilinePieces( ) } +export function noquote(): string { + throw new Error('No quote function is defined: https://ï.at/no-quote-func') +} + export function quote(arg: string) { if (/^[a-z0-9/_.\-@:=]+$/i.test(arg) || arg === '') { return arg diff --git a/test/core.test.js b/test/core.test.js index d7f164474c..a032bdaefc 100644 --- a/test/core.test.js +++ b/test/core.test.js @@ -263,25 +263,26 @@ describe('core', () => { } }) - test('cd() does affect parallel contexts', async () => { + test('cd() does not affect parallel contexts ($.cwdSyncHook enabled)', async () => { + syncProcessCwd() const cwd = process.cwd() try { fs.mkdirpSync('/tmp/zx-cd-parallel/one/two') await Promise.all([ within(async () => { assert.equal(process.cwd(), cwd) - await sleep(1) cd('/tmp/zx-cd-parallel/one') + await sleep(Math.random() * 15) assert.ok(process.cwd().endsWith('/tmp/zx-cd-parallel/one')) }), within(async () => { assert.equal(process.cwd(), cwd) - await sleep(2) + await sleep(Math.random() * 15) assert.equal(process.cwd(), cwd) }), within(async () => { assert.equal(process.cwd(), cwd) - await sleep(3) + await sleep(Math.random() * 15) $.cwd = '/tmp/zx-cd-parallel/one/two' assert.equal(process.cwd(), cwd) assert.ok( @@ -297,6 +298,7 @@ describe('core', () => { } finally { fs.rmSync('/tmp/zx-cd-parallel', { recursive: true }) cd(cwd) + syncProcessCwd(false) } }) diff --git a/test/index.test.js b/test/index.test.js index fab297bf2c..20b4fe3d82 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -20,6 +20,9 @@ import { $, log, cd, + syncProcessCwd, + usePowerShell, + useBash, kill, ProcessOutput, ProcessPromise, @@ -60,10 +63,13 @@ describe('index', () => { assert(ProcessOutput) assert(ProcessPromise) assert(cd) + assert(syncProcessCwd) assert(log) assert(kill) assert(defaults) assert(within) + assert(usePowerShell) + assert(useBash) // goods assert(argv) diff --git a/test/package.test.js b/test/package.test.js index c8d43b7bd2..5ec48b2a2c 100644 --- a/test/package.test.js +++ b/test/package.test.js @@ -13,10 +13,12 @@ // limitations under the License. import assert from 'node:assert' -import { test, describe, beforeEach } from 'node:test' +import { test, describe, beforeEach, before, after } from 'node:test' import '../build/globals.js' describe('package', () => { + before(() => syncProcessCwd()) + after(() => syncProcessCwd(false)) beforeEach(async () => { const pack = await $`npm pack` await $`tar xf ${pack}` diff --git a/test/smoke/win32.test.js b/test/smoke/win32.test.js index c32c0e140b..1b2c760a61 100644 --- a/test/smoke/win32.test.js +++ b/test/smoke/win32.test.js @@ -24,7 +24,7 @@ _describe('win32', () => { assert.match(p.stdout, /bash/) await within(async () => { - setupPowerShell() + usePowerShell() assert.match($.shell, /powershell/i) const p = await $`get-host` assert.match(p.stdout, /PowerShell/) @@ -33,7 +33,7 @@ _describe('win32', () => { test('quotePowerShell works', async () => { await within(async () => { - setupPowerShell() + usePowerShell() const p = await $`echo ${`Windows 'rulez!'`}` assert.match(p.stdout, /Windows 'rulez!'/) })