Skip to content

Commit

Permalink
refactor: improve ctx API
Browse files Browse the repository at this point in the history
  • Loading branch information
antongolub committed May 31, 2022
1 parent 2e45a01 commit 34d7fa9
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 12 deletions.
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
```
Expand Down
11 changes: 9 additions & 2 deletions src/experimental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -79,3 +77,12 @@ export function startSpinner(title = '') {
clearInterval(id)
)(setInterval(spin, 100))
}

export function ctx(cb: (_$: typeof $) => any) {
const _$ = Object.assign($.bind(null), getCtx())
function _cb() {
return cb(_$)
}

return runInCtx(_$, _cb)
}
111 changes: 111 additions & 0 deletions test/experimental.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ import {
retry,
startSpinner,
withTimeout,
ctx,
} from '../build/experimental.js'

import chalk from 'chalk'

import { ProcessPromise } from '../build/core.js'
import { randomId } from '../build/util.js'

$.verbose = false

test('retry works', async () => {
Expand Down Expand Up @@ -71,4 +75,111 @@ 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)

$.o =
(opts) =>
(...args) =>
ctx(($) => Object.assign($, opts)(...args))

const createHook = (opts, name = randomId(), cb = (v) => v, configurable) => {
ProcessPromise.prototype[name] = function (...args) {
Object.assign(this.ctx, opts)
return cb(this, ...args)
}

const getP = (p, opts, $args) =>
p instanceof ProcessPromise ? p : $.o(opts)(...$args)

return (...args) => {
if (!configurable) {
const p = getP(args[0], opts, args)
return p[name]()
}

return (...$args) => {
const p = getP($args[0], opts, $args)
return p[name](...args)
}
}
}

const quiet = createHook({ verbose: false }, 'quiet')
const debug = createHook({ verbose: 2 }, 'debug')

const timeout = createHook(
null,
'timeout',
(p, t, signal) => {
if (!t) return p
let timer = setTimeout(() => p.kill(signal), t)

return Object.assign(
p.finally(() => clearTimeout(timer)),
p
)
},
true
)

await quiet`echo 'quiet'`
await debug($`echo 'debug'`)
await $`echo 'chained'`.quiet()

try {
await quiet(timeout(100, 'SIGKILL')`sleep 9999`)
} catch {
console.log('killed1')
}

try {
const p = $`sleep 9999`
await quiet(timeout(100, 'SIGKILL')(p))
} catch {
console.log('killed2')
}

try {
await $`sleep 9999`.quiet().timeout(100, 'SIGKILL')
} catch {
console.log('killed3')
}

$.verbose = false
})

test.run()
2 changes: 1 addition & 1 deletion test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ 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'

test('only stdout is used during command substitution', async () => {
let hello = await $`echo Error >&2; echo Hello`
Expand Down

0 comments on commit 34d7fa9

Please sign in to comment.