Commit 504a844

Anton Golub <mailbox@antongolub.ru>
2022-03-18 11:33:07
feat: introduce experimental `withTimeout` helper (#355)
closes #349
1 parent f65b3af
src/experimental.d.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {ProcessOutput} from './index'
+import {ZxTemplate} from './index'
 
 interface Echo {
   (pieces: TemplateStringsArray, ...args: any[]): void
@@ -20,10 +20,9 @@ interface Echo {
 }
 export const echo: Echo
 
-interface Retry {
-  (pieces: TemplateStringsArray, ...args: any[]): Promise<ProcessOutput>
-}
-export const retry: (count?: number, delay?: number) => Retry
+export const retry: (count?: number, delay?: number) => ZxTemplate
+
+export const withTimeout: (delay?: number, signal?: string | number) => ZxTemplate
 
 type StopSpinner = () => void
 export function startSpinner(title: string): StopSpinner
src/experimental.mjs
@@ -25,6 +25,16 @@ export const retry = (count = 5, delay = 0) => async (cmd, ...args) => {
   }
 }
 
+// Runs and sets a timeout for a cmd
+export const withTimeout = (timeout, signal) => async (cmd, ...args) => {
+  let p = $(cmd, ...args)
+  if (!timeout) return p
+
+  let timer = setTimeout(() => p.kill(signal), timeout)
+
+  return p.finally(() => clearTimeout(timer))
+}
+
 // A console.log() alternative which can take ProcessOutput.
 export function echo(pieces, ...args) {
   if (Array.isArray(pieces) && pieces.every(isString) && pieces.length - 1 === args.length) {
src/index.d.ts
@@ -24,9 +24,11 @@ import _fetch from 'node-fetch'
 import {ParsedArgs} from 'minimist'
 import * as _which from 'which'
 
-interface $ {
+export interface ZxTemplate {
   (pieces: TemplateStringsArray, ...args: any[]): ProcessPromise<ProcessOutput>
+}
 
+interface $ extends ZxTemplate {
   verbose: boolean
   shell: string
   prefix: string
README.md
@@ -418,6 +418,16 @@ await $`long-running command`
 stop()
 ```
 
+#### `withTimeout()`
+
+Runs and sets a timeout for a cmd.
+
+```js
+import {withTimeout} from 'zx/experimental'
+
+await withTimeout(100, 'SIGTERM')`sleep 9999`
+```
+
 ### FAQ
 
 #### Passing env variables
test.mjs
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {strict as assert} from 'assert'
-import {retry} from './src/experimental.mjs'
+import {retry, withTimeout} from './src/experimental.mjs'
 
 let всегоТестов = 0
 
@@ -245,6 +245,10 @@ if (test('YAML works')) {
   console.log(chalk.greenBright('YAML works'))
 }
 
+if (test('which available')) {
+  assert.equal(which.sync('npm'), await which('npm'))
+}
+
 if (test('Retry works')) {
   let exitCode = 0
   let now = Date.now()
@@ -257,8 +261,17 @@ if (test('Retry works')) {
   assert(Date.now() >= now + 50 * (5 - 1))
 }
 
-if (test('which available')) {
-  assert.equal(which.sync('npm'), await which('npm'))
+if (test('withTimeout works')) {
+  let exitCode = 0
+  let signal
+  try {
+    await withTimeout(100, 'SIGKILL')`sleep 9999`
+  } catch (p) {
+    exitCode = p.exitCode
+    signal = p.signal
+  }
+  assert.equal(exitCode, null)
+  assert.equal(signal, 'SIGKILL')
 }
 
 let version