Commit e652772
Changed files (7)
.github
workflows
.github/workflows/test.yml
@@ -2,9 +2,9 @@ name: Test
on:
push:
- branches: [main]
+ branches: [main, v6]
pull_request:
- branches: [main]
+ branches: [main, v6]
jobs:
test:
src/context.ts
@@ -14,20 +14,38 @@
import { AsyncLocalStorage } from 'node:async_hooks'
-let root: any
+export type Options = {
+ verbose: boolean
+ cwd: string
+ env: NodeJS.ProcessEnv
+ prefix: string
+ shell: string
+ maxBuffer: number
+ quote: (v: string) => string
+}
+
+export type Context = Options & {
+ nothrow?: boolean
+ cmd: string
+ __from: string
+ resolve: any
+ reject: any
+}
+
+let root: Options
-const storage = new AsyncLocalStorage<any>()
+const storage = new AsyncLocalStorage<Options>()
export function getCtx() {
- return storage.getStore()
+ return storage.getStore() as Context
}
-export function setRootCtx(ctx: any) {
+export function setRootCtx(ctx: Options) {
storage.enterWith(ctx)
root = ctx
}
export function getRootCtx() {
return root
}
-export function runInCtx(ctx: any, cb: any) {
+export function runInCtx(ctx: Options, cb: any) {
return storage.run(ctx, cb)
}
src/core.ts
@@ -23,7 +23,7 @@ import { inspect, promisify } from 'node:util'
import { spawn } from 'node:child_process'
import { chalk, which } from './goods.js'
-import { runInCtx, getCtx, setRootCtx } from './context.js'
+import { runInCtx, getCtx, setRootCtx, Context } from './context.js'
import { printStd, printCmd } from './print.js'
import { quote, substitute } from './guards.js'
@@ -75,20 +75,6 @@ try {
$.prefix = 'set -euo pipefail;'
} catch (e) {}
-type Options = {
- nothrow: boolean
- verbose: boolean
- cmd: string
- cwd: string
- env: NodeJS.ProcessEnv
- prefix: string
- shell: string
- maxBuffer: number
- __from: string
- resolve: any
- reject: any
-}
-
export class ProcessPromise extends Promise<ProcessOutput> {
child?: ChildProcessByStdio<Writable, Readable, Readable>
_resolved = false
@@ -96,7 +82,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
_piped = false
_prerun: any = undefined
_postrun: any = undefined
- ctx?: Options
+ ctx?: Context
get stdin() {
this._inheritStdin = false
@@ -176,7 +162,7 @@ export class ProcessPromise extends Promise<ProcessOutput> {
if (this.child) return // The _run() called from two places: then() and setTimeout().
if (this._prerun) this._prerun() // In case $1.pipe($2), the $2 returned, and on $2._run() invoke $1._run().
- runInCtx(this.ctx, () => {
+ runInCtx(this.ctx!, () => {
const {
nothrow,
cmd,
src/experimental.ts
@@ -17,8 +17,6 @@ import { sleep } from './goods.js'
import { isString } from './util.js'
import { getCtx, runInCtx } from './context.js'
-export { getCtx, runInCtx }
-
// Retries a command a few times. Will return after the first
// successful attempt, or will throw after specifies attempts count.
export function retry(count = 5, delay = 0) {
@@ -79,3 +77,14 @@ export function startSpinner(title = '') {
clearInterval(id)
)(setInterval(spin, 100))
}
+
+export function ctx(
+ cb: Parameters<typeof runInCtx>[1]
+): ReturnType<typeof runInCtx> {
+ const _$ = Object.assign($.bind(null), getCtx())
+ function _cb() {
+ return cb(_$)
+ }
+
+ return runInCtx(_$, _cb)
+}
test/experimental.test.js
@@ -21,6 +21,7 @@ import {
retry,
startSpinner,
withTimeout,
+ ctx,
} from '../build/experimental.js'
import chalk from 'chalk'
@@ -71,4 +72,42 @@ test('spinner works', async () => {
s()
})
+test('ctx() provides isolates running scopes', async () => {
+ $.verbose = true
+
+ await ctx(async ($) => {
+ $.verbose = false
+ await $`echo a`
+
+ $.verbose = true
+ await $`echo b`
+
+ $.verbose = false
+ await ctx(async ($) => {
+ await $`echo d`
+
+ await ctx(async ($) => {
+ assert.ok($.verbose === false)
+
+ await $`echo e`
+ $.verbose = true
+ })
+ $.verbose = true
+ })
+
+ await $`echo c`
+ })
+
+ await $`echo f`
+
+ await ctx(async ($) => {
+ assert.is($.verbose, true)
+ $.verbose = false
+ await $`echo g`
+ })
+
+ assert.is($.verbose, true)
+ $.verbose = false
+})
+
test.run()
test/index.test.js
@@ -20,7 +20,9 @@ import { Writable } from 'node:stream'
import { Socket } from 'node:net'
import '../build/globals.js'
import { ProcessPromise } from '../build/index.js'
-import {getCtx, runInCtx} from '../build/experimental.js'
+import { getCtx, runInCtx } from '../build/context.js'
+
+$.verbose = false
test('only stdout is used during command substitution', async () => {
let hello = await $`echo Error >&2; echo Hello`
@@ -48,6 +50,7 @@ test('undefined and empty string correctly quoted', async () => {
$.verbose = true
assert.is((await $`echo -n ${undefined}`).toString(), 'undefined')
assert.is((await $`echo -n ${''}`).toString(), '')
+ $.verbose = false
})
test('can create a dir with a space in the name', async () => {
@@ -214,8 +217,6 @@ test('globby available', async () => {
assert.is(typeof globby.isDynamicPattern, 'function')
assert.is(typeof globby.isGitIgnored, 'function')
assert.is(typeof globby.isGitIgnoredSync, 'function')
- console.log(chalk.greenBright('globby available'))
-
assert.equal(await globby('*.md'), ['README.md'])
})
README.md
@@ -438,25 +438,26 @@ import {withTimeout} from 'zx/experimental'
await withTimeout(100, 'SIGTERM')`sleep 9999`
```
-### `getCtx()` and `runInCtx()`
+### `ctx()`
-[async_hooks](https://nodejs.org/api/async_hooks.html) methods to manipulate bound context.
-This object is used by zx inners, so it has a significant impact on the call mechanics. Please use this carefully and wisely.
+[async_hooks](https://nodejs.org/api/async_hooks.html)-driven scope isolator.
+Creates a separate zx-context for the specified function.
```js
-import {getCtx, runInCtx} from 'zx/experimental'
+import {ctx} from 'zx/experimental'
-runInCtx({ ...getCtx() }, async () => {
+const _$ = $
+ctx(async ($) => {
await sleep(10)
cd('/foo')
// $.cwd refers to /foo
- // getCtx().cwd === $.cwd
+ // _$.cwd === $.cwd
})
-runInCtx({ ...getCtx() }, async () => {
+ctx(async ($) => {
await sleep(20)
- // $.cwd refers to /foo
- // but getCtx().cwd !== $.cwd
+ // _$.cwd refers to /foo
+ // but _$.cwd !== $.cwd
})
```