Commit 5b0a0cd

Anton Medvedev <anton@medv.io>
2021-05-24 21:20:17
Add nothrow()
1 parent 31c97b2
index.d.ts
@@ -38,6 +38,7 @@ export interface ProcessPromise<T> extends Promise<T> {
   readonly stdin: Writable
   readonly stdout: Readable
   readonly stderr: Readable
+  readonly exitCode: Promise<number>
 
   pipe(dest: ProcessPromise<ProcessOutput> | Writable): ProcessPromise<ProcessOutput>
 }
index.mjs
@@ -53,7 +53,7 @@ export function $(pieces, ...args) {
           code, stdout, stderr, combined,
           message: `${stderr || '\n'}    at ${__from}`
         });
-        (code === 0 ? resolve : reject)(output)
+        (code === 0 || promise._nothrow ? resolve : reject)(output)
       })
     })
   })
@@ -136,9 +136,15 @@ export async function fetch(url, init) {
 
 export const sleep = promisify(setTimeout)
 
+export function nothrow(promise) {
+  promise._nothrow = true
+  return promise
+}
+
 export class ProcessPromise extends Promise {
   child = undefined
   _stop = () => void 0
+  _nothrow = false
 
   get stdin() {
     return this.child.stdin
@@ -152,6 +158,12 @@ export class ProcessPromise extends Promise {
     return this.child.stderr
   }
 
+  get exitCode() {
+    return this
+      .then(p => p.exitCode)
+      .catch(p => p.exitCode)
+  }
+
   pipe(dest) {
     if (typeof dest === 'string') {
       throw new Error('The pipe() method does not take strings. Forgot $?')
@@ -217,6 +229,7 @@ Object.assign(global, {
   chalk,
   fetch,
   fs: {...fs, createWriteStream, createReadStream},
+  nothrow,
   os,
   question,
   sleep,
README.md
@@ -95,6 +95,7 @@ class ProcessPromise<T> extends Promise<T> {
   readonly stdin: Writable
   readonly stdout: Readable
   readonly stderr: Readable
+  readonly exitCode: Promise<number>
   pipe(dest): ProcessPromise<T>
 }
 ```
@@ -172,6 +173,34 @@ Usage:
 await sleep(1000)
 ```
 
+### `nothrow()`
+
+Changes behavior of `$` to not throw an exception on non-zero exit codes.
+
+```ts
+function nothrow(p: ProcessPromise<ProcessOutput>): ProcessPromise<ProcessOutput>
+```
+
+Usage:
+
+```js
+await nothrow($`grep something from-file`)
+
+// Inside a pipe():
+
+await $`find ./examples -type f -print0`
+  .pipe(nothrow($`xargs -0 grep something`))
+  .pipe($`wc -l`)
+```
+
+If only the `exitCode` is needed, you can use the next code instead:
+
+```js
+if (await $`[[ -d path ]]`.exitCode == 0) {...}
+// Equivalent of:
+if ((await nothrow($`[[ -d path ]]`)).exitCode == 0) {...}
+```
+
 ### `chalk` package
 
 The [chalk](https://www.npmjs.com/package/chalk) package is available without 
@@ -289,8 +318,7 @@ zx examples/index.md
 
 ### TypeScript scripts
 
-The `zx` can compile `.ts` scripts to `.mjs` by running `tsc` (should be 
-installed on local machine).
+The `zx` can compile `.ts` scripts to `.mjs` and execute them.
 
 ```bash
 zx examples/typescript.ts
test.mjs
@@ -119,6 +119,16 @@ import {strict as assert} from 'assert'
   console.log('☝️ Error above is expected')
 }
 
+{ // ProcessOutput::exitCode doesn't throw
+  assert(await $`grep qwerty README.md`.exitCode !== 0)
+  assert(await $`[[ -f ${__filename} ]]`.exitCode === 0)
+}
+
+{ // nothrow() doesn't throw
+  let {exitCode} = await nothrow($`exit 42`)
+  assert(exitCode === 42)
+}
+
 { // require() is working in ESM
   const {name, version} = require('./package.json')
   assert(typeof name === 'string')