Repository: oclif/core
Pull request #99 (2021-01-07 18:46:17)

feat: parse async
requires #94 

codecov[bot]: # [Codecov](https://codecov.io/gh/oclif/core/pull/99?src=pr&el=h1) Report
> Merging [#99](https://codecov.io/gh/oclif/core/pull/99?src=pr&el=desc) (ba87ecd) into [master](https://codecov.io/gh/oclif/core/commit/4b44270a1f584d8ee4995b41d93282b24ce11440?el=desc) (4b44270) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/oclif/core/pull/99/graphs/tree.svg?width=650&height=150&src=pr&token=K0kGk9tefA)](https://codecov.io/gh/oclif/core/pull/99?src=pr&el=tree)

```diff
@@          Coverage Diff           @@
##           master     #99   +/-   ##
======================================
  Coverage    0.00%   0.00%           
======================================
  Files           3       3           
  Lines          15      15           
  Branches        2       2           
======================================
  Misses         15      15           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/oclif/core/pull/99?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/oclif/core/pull/99?src=pr&el=footer). Last update [4b44270...ba87ecd](https://codecov.io/gh/oclif/core/pull/99?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/command.ts b/src/command.ts
index c482063..0914577 100644
--- a/src/command.ts
+++ b/src/command.ts
@@ -154,7 +154,7 @@ export default abstract class Command {
     g['http-call']!.userAgent = this.config.userAgent
   }
 
-  protected parse<F, A extends { [name: string]: any }>(options?: Interfaces.Input<F>, argv = this.argv): Interfaces.ParserOutput<F, A> {
+  protected async parse<F, A extends { [name: string]: any }>(options?: Interfaces.Input<F>, argv = this.argv): Promise<Interfaces.ParserOutput<F, A>> {
     if (!options) options = this.constructor as any
     return Parser.parse(argv, {context: this, ...options})
   }
diff --git a/src/flags.ts b/src/flags.ts
index 7afca72..6983a42 100644
--- a/src/flags.ts
+++ b/src/flags.ts
@@ -14,7 +14,7 @@ export function option<T>(options: {parse: OptionFlag<T>['parse']} & Partial<Opt
 
 const _enum = <T = string>(opts: EnumFlagOptions<T>): OptionFlag<T> => {
   return build<T>({
-    parse(input) {
+    async parse(input) {
       if (!opts.options.includes(input)) throw new Error(`Expected --${this.name}=${input} to be one of: ${opts.options.join(', ')}`)
       return input as unknown as T
     },
@@ -33,7 +33,7 @@ export const version = (opts: Partial<BooleanFlag<boolean>> = {}) => {
     // char: 'v',
     description: 'show CLI version',
     ...opts,
-    parse: (_: any, cmd: Command) => {
+    parse: async (_: any, cmd: Command) => {
       cmd.log(cmd.config.userAgent)
       cmd.exit(0)
     },
@@ -45,7 +45,7 @@ export const help = (opts: Partial<BooleanFlag<boolean>> = {}) => {
     // char: 'h',
     description: 'show CLI help',
     ...opts,
-    parse: (_: any, cmd: Command) => {
+    parse: async (_: any, cmd: Command) => {
       (cmd as any)._help()
     },
   })
diff --git a/src/interfaces/parser.ts b/src/interfaces/parser.ts
index b65bf26..0e5ffa7 100644
--- a/src/interfaces/parser.ts
+++ b/src/interfaces/parser.ts
@@ -1,7 +1,7 @@
 import {AlphabetLowercase, AlphabetUppercase} from './alphabet'
 import {Config} from './config'
 
-export type ParseFn<T> = (input: string) => T
+export type ParseFn<T> = (input: string) => Promise<T>
 
 export interface Arg<T = string> {
   name: string;
@@ -18,7 +18,7 @@ export interface ArgBase<T> {
   description?: string;
   hidden?: boolean;
   parse: ParseFn<T>;
-  default?: T | (() => T);
+  default?: T | (() => Promise<T>);
   input?: string;
   options?: string[];
 }
@@ -77,7 +77,7 @@ export type DefaultContext<T> = {
   flags: { [k: string]: string };
 }
 
-export type Default<T> = T | ((context: DefaultContext<T>) => T)
+export type Default<T> = T | ((context: DefaultContext<T>) => Promise<T>)
 
 export type FlagBase<T, I> = {
   name: string;
@@ -93,7 +93,7 @@ export type FlagBase<T, I> = {
    * also accept an environment variable as input
    */
   env?: string;
-  parse(input: I, context: any): T;
+  parse(input: I, context: any): Promise<T>;
 }
 
 export type BooleanFlag<T> = FlagBase<T, boolean> & {
diff --git a/src/parser/flags.ts b/src/parser/flags.ts
index cac0f0b..75758bc 100644
--- a/src/parser/flags.ts
+++ b/src/parser/flags.ts
@@ -11,7 +11,7 @@ export function build(
 export function build<T>(defaults: Partial<OptionFlag<T>>): Definition<T> {
   return (options: any = {}): any => {
     return {
-      parse: (i: string, _: any) => i,
+      parse: async (i: string, _: any) => i,
       ...defaults,
       ...options,
       input: [] as string[],
@@ -25,7 +25,7 @@ export function boolean<T = boolean>(
   options: Partial<BooleanFlag<T>> = {},
 ): BooleanFlag<T> {
   return {
-    parse: (b, _) => b,
+    parse: async (b, _) => b,
     ...options,
     allowNo: Boolean(options.allowNo),
     type: 'boolean',
@@ -33,7 +33,7 @@ export function boolean<T = boolean>(
 }
 
 export const integer = build({
-  parse: input => {
+  parse: async input => {
     if (!/^-?\d+$/.test(input))
       throw new Error(`Expected an integer but received: ${input}`)
     return parseInt(input, 10)
diff --git a/src/parser/index.ts b/src/parser/index.ts
index ea05449..2850f49 100644
--- a/src/parser/index.ts
+++ b/src/parser/index.ts
@@ -15,7 +15,7 @@ const m = Deps()
 // eslint-disable-next-line node/no-missing-require
 .add('validate', () => require('./validate').validate as typeof Validate.validate)
 
-export function parse<TFlags, TArgs extends { [name: string]: string }>(argv: string[], options: Input<TFlags>): ParserOutput<TFlags, TArgs> {
+export async function parse<TFlags, TArgs extends { [name: string]: string }>(argv: string[], options: Input<TFlags>): Promise<ParserOutput<TFlags, TArgs>> {
   const input = {
     argv,
     context: options.context,
@@ -28,7 +28,7 @@ export function parse<TFlags, TArgs extends { [name: string]: string }>(argv: st
     strict: options.strict !== false,
   }
   const parser = new Parser(input)
-  const output = parser.parse()
+  const output = await parser.parse()
   m.validate({input, output})
   return output as any
 }
diff --git a/src/parser/parse.ts b/src/parser/parse.ts
index be18ea8..2304e2e 100644
--- a/src/parser/parse.ts
+++ b/src/parser/parse.ts
@@ -45,7 +45,7 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
     this.metaData = {}
   }
 
-  public parse() {
+  public async parse() {
     this._debugInput()
 
     const findLongFlag = (arg: string) => {
@@ -125,9 +125,9 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
       if (arg) arg.input = input
       this.raw.push({type: 'arg', input})
     }
-    const argv = this._argv()
+    const argv = await this._argv()
     const args = this._args(argv)
-    const flags = this._flags()
+    const flags = await this._flags()
     this._debugOutput(argv, args, flags)
     return {
       args,
@@ -147,7 +147,7 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
     return args
   }
 
-  private _flags(): TFlags {
+  private async _flags(): Promise<TFlags> {
     const flags = {} as any
     this.metaData.flags = {} as any
     for (const token of this._flagTokens) {
@@ -159,13 +159,15 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
         } else {
           flags[token.flag] = true
         }
-        flags[token.flag] = flag.parse(flags[token.flag], this.context)
+        // eslint-disable-next-line no-await-in-loop
+        flags[token.flag] = await flag.parse(flags[token.flag], this.context)
       } else {
         const input = token.input
         if (flag.options && !flag.options.includes(input)) {
           throw new m.errors.FlagInvalidOptionError(flag, input)
         }
-        const value = flag.parse ? flag.parse(input, this.context) : input
+        // eslint-disable-next-line no-await-in-loop
+        const value = flag.parse ? await flag.parse(input, this.context) : input
         if (flag.multiple) {
           flags[token.flag] = flags[token.flag] || []
           flags[token.flag].push(value)
@@ -179,12 +181,14 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
       if (flags[k]) continue
       if (flag.type === 'option' && flag.env) {
         const input = process.env[flag.env]
-        if (input) flags[k] = flag.parse(input, this.context)
+        // eslint-disable-next-line no-await-in-loop
+        if (input) flags[k] = await flag.parse(input, this.context)
       }
       if (!(k in flags) && flag.default !== undefined) {
         this.metaData.flags[k] = {setFromDefault: true}
         if (typeof flag.default === 'function') {
-          flags[k] = flag.default({options: flag, flags, ...this.context})
+          // eslint-disable-next-line no-await-in-loop
+          flags[k] = await flag.default({options: flag, flags, ...this.context})
         } else {
           flags[k] = flag.default
         }
@@ -193,7 +197,7 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
     return flags
   }
 
-  private _argv(): any[] {
+  private async _argv(): Promise<any[]> {
     const args: any[] = []
     const tokens = this._argTokens
     for (let i = 0; i < Math.max(this.input.args.length, tokens.length); i++) {
@@ -204,13 +208,16 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
           if (arg.options && !arg.options.includes(token.input)) {
             throw new m.errors.ArgInvalidOptionError(arg, token.input)
           }
-          args[i] = arg.parse(token.input)
+          // eslint-disable-next-line no-await-in-loop
+          args[i] = await arg.parse(token.input)
         } else {
           args[i] = token.input
         }
       } else if ('default' in arg) {
         if (typeof arg.default === 'function') {
-          args[i] = arg.default()
+          // eslint-disable-next-line no-await-in-loop
+          const f = await arg.default()
+          args[i] = f
         } else {
           args[i] = arg.default
         }


Test patch:
diff --git a/test/command/command.test.ts b/test/command/command.test.ts
index 1b7f070..213a743 100644
--- a/test/command/command.test.ts
+++ b/test/command/command.test.ts
@@ -208,7 +208,7 @@ describe('command', () => {
         }
 
         async run() {
-          const {flags} = this.parse(CMD)
+          const {flags} = await this.parse(CMD)
           this.log(flags.foo)
         }
       }
diff --git a/test/parser/parse.test.ts b/test/parser/parse.test.ts
index 979ea05..fc14374 100644
--- a/test/parser/parse.test.ts
+++ b/test/parser/parse.test.ts
@@ -5,8 +5,8 @@ import {flags, parse} from '../../src/parser'
 import {Interfaces} from '../../src'
 
 describe('parse', () => {
-  it('--bool', () => {
-    const out = parse(['--bool'], {
+  it('--bool', async () => {
+    const out = await parse(['--bool'], {
       flags: {
         bool: flags.boolean(),
       },
@@ -14,16 +14,16 @@ describe('parse', () => {
     expect(out).to.deep.include({flags: {bool: true}})
   })
 
-  it('arg1', () => {
-    const out = parse(['arg1'], {
+  it('arg1', async () => {
+    const out = await parse(['arg1'], {
       args: [{name: 'foo'}],
     })
     expect(out.argv).to.deep.equal(['arg1'])
     expect(out.args).to.deep.equal({foo: 'arg1'})
   })
 
-  it('arg1 arg2', () => {
-    const out = parse(['arg1', 'arg2'], {
+  it('arg1 arg2', async () => {
+    const out = await parse(['arg1', 'arg2'], {
       args: [{name: 'foo'}, {name: 'bar'}],
     })
     expect(out.argv).to.deep.equal(['arg1', 'arg2'])
@@ -31,8 +31,8 @@ describe('parse', () => {
   })
 
   describe('output: array', () => {
-    it('--bool', () => {
-      const out = parse(['--bool'], {
+    it('--bool', async () => {
+      const out = await parse(['--bool'], {
         flags: {
           bool: flags.boolean(),
         },
@@ -40,15 +40,15 @@ describe('parse', () => {
       expect(out.raw[0]).to.deep.include({flag: 'bool'})
     })
 
-    it('arg1', () => {
-      const out = parse(['arg1'], {
+    it('arg1', async () => {
+      const out = await parse(['arg1'], {
         args: [{name: 'foo'}],
       })
       expect(out.raw[0]).to.have.property('input', 'arg1')
     })
 
-    it('parses args and flags', () => {
-      const out = parse(['foo', '--myflag', 'bar', 'baz'], {
+    it('parses args and flags', async () => {
+      const out = await parse(['foo', '--myflag', 'bar', 'baz'], {
         args: [{name: 'myarg'}, {name: 'myarg2'}],
         flags: {myflag: flags.string()},
       })
@@ -58,16 +58,16 @@ describe('parse', () => {
     })
 
     describe('flags', () => {
-      it('parses flags', () => {
-        const out = parse(['--myflag', '--myflag2'], {
+      it('parses flags', async () => {
+        const out = await parse(['--myflag', '--myflag2'], {
           flags: {myflag: flags.boolean(), myflag2: flags.boolean()},
         })
         expect(Boolean(out.flags.myflag)).to.equal(true)
         expect(Boolean(out.flags.myflag2)).to.equal(true)
       })
 
-      it('parses short flags', () => {
-        const out = parse(['-mf'], {
+      it('parses short flags', async () => {
+        const out = await parse(['-mf'], {
           flags: {
             force: flags.boolean({char: 'f'}),
             myflag: flags.boolean({char: 'm'}),
@@ -77,8 +77,8 @@ describe('parse', () => {
         expect(Boolean(out.flags.force)).to.equal(true)
       })
     })
-    it('parses flag value with "=" to separate', () => {
-      const out = parse(['--myflag=foo'], {
+    it('parses flag value with "=" to separate', async () => {
+      const out = await parse(['--myflag=foo'], {
         flags: {
           myflag: flags.string({char: 'm'}),
         },
@@ -86,8 +86,8 @@ describe('parse', () => {
       expect(out.flags).to.deep.equal({myflag: 'foo'})
     })
 
-    it('parses flag value with "=" in value', () => {
-      const out = parse(['--myflag', '=foo'], {
+    it('parses flag value with "=" in value', async () => {
+      const out = await parse(['--myflag', '=foo'], {
         flags: {
           myflag: flags.string({char: 'm'}),
         },
@@ -95,8 +95,8 @@ describe('parse', () => {
       expect(out.flags).to.deep.equal({myflag: '=foo'})
     })
 
-    it('parses short flag value with "="', () => {
-      const out = parse(['-m=foo'], {
+    it('parses short flag value with "="', async () => {
+      const out = await parse(['-m=foo'], {
         flags: {
           myflag: flags.string({char: 'm'}),
         },
@@ -104,8 +104,8 @@ describe('parse', () => {
       expect(out.flags).to.deep.equal({myflag: 'foo'})
     })
 
-    it('parses value of ""', () => {
-      const out = parse(['-m', ''], {
+    it('parses value of ""', async () => {
+      const out = await parse(['-m', ''], {
         flags: {
           myflag: flags.string({char: 'm'}),
         },
@@ -113,9 +113,10 @@ describe('parse', () => {
       expect(out.flags).to.deep.equal({myflag: ''})
     })
 
-    it('requires required flag', () => {
-      expect(() => {
-        parse([], {
+    it('requires required flag', async () => {
+      let message = ''
+      try {
+        await parse([], {
           flags: {
             myflag: flags.string({
               description: 'flag description',
@@ -123,13 +124,16 @@ describe('parse', () => {
             }),
           },
         })
-      }).to.throw(
+      } catch (error) {
+        message = error.message
+      }
+      expect(message).to.equal(
         'Missing required flag:\n --myflag MYFLAG  flag description\nSee more help with --help',
       )
     })
 
-    it('removes flags from argv', () => {
-      const out = parse(['--myflag', 'bar', 'foo'], {
+    it('removes flags from argv', async () => {
+      const out = await parse(['--myflag', 'bar', 'foo'], {
         args: [{name: 'myarg'}],
         flags: {myflag: flags.string()},
       })
@@ -138,9 +142,10 @@ describe('parse', () => {
     })
 
     describe('args', () => {
-      it('requires required args with names', () => {
-        expect(() => {
-          parse(['arg1'], {
+      it('requires required args with names', async () => {
+        let message = ''
+        try {
+          await parse(['arg1'], {
             args: [
               {name: 'arg1', required: true},
               {
@@ -155,35 +160,42 @@ describe('parse', () => {
               },
             ],
           })
-        }).to.throw(`Missing 2 required args:
+        } catch (error) {
+          message = error.message
+        }
+        expect(message).to.equal(`Missing 2 required args:
 arg2  arg2 desc
 arg3  arg3 desc
 See more help with --help`)
       })
 
-      it('too many args', () => {
-        expect(() => {
-          parse(['arg1', 'arg2'], {
+      it('too many args', async () => {
+        let message = ''
+        try {
+          await parse(['arg1', 'arg2'], {
             args: [{name: 'arg1', required: true}],
           })
-        }).to.throw('Unexpected argument: arg2\nSee more help with --help')
+        } catch (error) {
+          message = error.message
+        }
+        expect(message).to.equal('Unexpected argument: arg2\nSee more help with --help')
       })
 
-      it('parses args', () => {
-        const out = parse(['foo', 'bar'], {
+      it('parses args', async () => {
+        const out = await parse(['foo', 'bar'], {
           args: [{name: 'myarg'}, {name: 'myarg2'}],
         })
         expect(out.argv).to.deep.equal(['foo', 'bar'])
       })
-      it('skips optional args', () => {
-        const out = parse(['foo'], {
+      it('skips optional args', async () => {
+        const out = await parse(['foo'], {
           args: [{name: 'myarg'}, {name: 'myarg2'}],
         })
         expect(out.argv).to.deep.equal(['foo'])
       })
 
-      it('skips non-required args', () => {
-        const out = parse(['foo'], {
+      it('skips non-required args', async () => {
+        const out = await parse(['foo'], {
           args: [
             {name: 'myarg', required: false},
             {name: 'myarg2', required: false},
@@ -192,15 +204,15 @@ See more help with --help`)
         expect(out.argv).to.deep.equal(['foo'])
       })
 
-      it('parses something looking like a flag as an arg', () => {
-        const out = parse(['--foo'], {
+      it('parses something looking like a flag as an arg', async () => {
+        const out = await parse(['--foo'], {
           args: [{name: 'myarg'}],
         })
         expect(out.argv).to.deep.equal(['--foo'])
       })
 
-      it('parses - as an arg', () => {
-        const out = parse(['-'], {
+      it('parses - as an arg', async () => {
+        const out = await parse(['-'], {
           args: [{name: 'myarg'}],
         })
         expect(out.argv).to.deep.equal(['-'])
@@ -208,60 +220,69 @@ See more help with --help`)
     })
 
     describe('args - no args passed in, with defaults', () => {
-      it('two args: only first is required, only second has a default', () => {
-        expect(() => {
-          parse([], {
+      it('two args: only first is required, only second has a default', async () => {
+        let message = ''
+        try {
+          await parse([], {
             args: [
               {name: 'arg1', required: true},
               {name: 'arg2', required: false, default: 'some_default'},
             ],
           })
-        }).to.throw(`Missing 1 required arg:
+        } catch (error) {
+          message = error.message
+        }
+        expect(message).to.equal(`Missing 1 required arg:
 arg1
 See more help with --help`)
       })
 
-      it('two args: only first is required, only first has a default', () => {
-        expect(() => {
-          parse([], {
-            args: [
-              {name: 'arg1', required: true, default: 'my_default'},
-              {name: 'arg2', required: false},
-            ],
-          })
-        }).to.not.throw()
+      it('two args: only first is required, only first has a default', async () => {
+        await parse([], {
+          args: [
+            {name: 'arg1', required: true, default: 'my_default'},
+            {name: 'arg2', required: false},
+          ],
+        })
+        // won't reach here if thrown
+        expect(() => {}).to.not.throw()
       })
 
-      it('two args: both have a default, only first is required', () => {
-        expect(() => {
-          parse([], {
-            args: [
-              {name: 'arg1', required: true, default: 'my_default'},
-              {name: 'arg2', required: false, default: 'some_default'},
-            ],
-          })
-        }).to.not.throw()
+      it('two args: both have a default, only first is required', async () => {
+        await parse([], {
+          args: [
+            {name: 'arg1', required: true, default: 'my_default'},
+            {name: 'arg2', required: false, default: 'some_default'},
+          ],
+        })
+        // won't reach here if thrown
+        expect(() => {}).to.not.throw()
       })
     })
 
     describe('optional args should always be after required args', () => {
-      it('required arg after optional arg', () => {
-        expect(() => {
-          parse([], {
+      it('required arg after optional arg', async () => {
+        let message = ''
+        try {
+          await parse([], {
             args: [
               {name: 'arg1', required: false},
               {name: 'arg2', required: true, default: 'some_default'},
             ],
           })
-        }).to.throw(`Invalid argument spec:
+        } catch (error) {
+          message = error.message
+        }
+        expect(message).to.equal(`Invalid argument spec:
 arg1 (optional)
 arg2 (required)
 See more help with --help`)
       })
 
-      it('required arg after multiple optional args', () => {
-        expect(() => {
-          parse([], {
+      it('required arg after multiple optional args', async () => {
+        let message = ''
+        try {
+          await parse([], {
             args: [
               {name: 'arg1', required: false},
               {name: 'arg2', required: false, default: 'my_default'},
@@ -269,7 +290,10 @@ See more help with --help`)
               {name: 'arg4', required: true},
             ],
           })
-        }).to.throw(`Invalid argument spec:
+        } catch (error) {
+          message = error.message
+        }
+        expect(message).to.equal(`Invalid argument spec:
 arg1 (optional)
 arg2 (optional)
 arg3 (optional)
@@ -279,8 +303,8 @@ See more help with --help`)
     })
 
     describe('multiple flags', () => {
-      it('parses multiple flags', () => {
-        const out = parse(['--bar', 'a', '--bar=b', '--foo=c', '--baz=d'], {
+      it('parses multiple flags', async () => {
+        const out = await parse(['--bar', 'a', '--bar=b', '--foo=c', '--baz=d'], {
           flags: {
             foo: flags.string(),
             bar: flags.string({multiple: true}),
@@ -294,8 +318,8 @@ See more help with --help`)
     })
 
     describe('strict: false', () => {
-      it('skips flag parsing after "--"', () => {
-        const out = parse(['foo', 'bar', '--', '--myflag'], {
+      it('skips flag parsing after "--"', async () => {
+        const out = await parse(['foo', 'bar', '--', '--myflag'], {
           args: [{name: 'argOne'}],
           flags: {myflag: flags.boolean()},
           strict: false,
@@ -305,8 +329,8 @@ See more help with --help`)
       })
 
       describe('--: false', () => {
-        it('can be disabled', () => {
-          const out = parse(['foo', 'bar', '--', '--myflag'], {
+        it('can be disabled', async () => {
+          const out = await parse(['foo', 'bar', '--', '--myflag'], {
             args: [{name: 'argOne'}],
             strict: false,
             '--': false,
@@ -316,8 +340,8 @@ See more help with --help`)
         })
       })
 
-      it('does not repeat arguments', () => {
-        const out = parse(['foo', '--myflag=foo bar'], {
+      it('does not repeat arguments', async () => {
+        const out = await parse(['foo', '--myflag=foo bar'], {
           strict: false,
         })
         expect(out.argv).to.deep.equal(['foo', '--myflag=foo bar'])
@@ -325,71 +349,83 @@ See more help with --help`)
     })
 
     describe('integer flag', () => {
-      it('parses integers', () => {
-        const out = parse(['--int', '100'], {
+      it('parses integers', async () => {
+        const out = await parse(['--int', '100'], {
           flags: {int: flags.integer(), s: flags.string()},
         })
         expect(out.flags).to.deep.include({int: 100})
       })
 
-      it('parses zero', () => {
-        const out = parse(['--int', '0'], {
+      it('parses zero', async () => {
+        const out = await parse(['--int', '0'], {
           flags: {int: flags.integer(), s: flags.string()},
         })
         expect(out.flags).to.deep.include({int: 0})
       })
 
-      it('parses negative integers', () => {
-        const out = parse(['--int', '-123'], {
+      it('parses negative integers', async () => {
+        const out = await parse(['--int', '-123'], {
           flags: {int: flags.integer(), s: flags.string()},
         })
         expect(out.flags).to.deep.include({int: -123})
       })
 
-      it('does not parse floats', () => {
-        expect(() => {
-          parse(['--int', '3.14'], {
+      it('does not parse floats', async () => {
+        let message = ''
+        try {
+          await parse(['--int', '3.14'], {
             flags: {int: flags.integer()},
           })
-        }).to.throw('Expected an integer but received: 3.14')
+        } catch (error) {
+          message = error.message
+        }
+        expect(message).to.equal('Expected an integer but received: 3.14')
       })
 
-      it('does not parse fractions', () => {
-        expect(() => {
-          parse(['--int', '3/4'], {
+      it('does not parse fractions', async () => {
+        let message = ''
+        try {
+          await parse(['--int', '3/4'], {
             flags: {int: flags.integer()},
           })
-        }).to.throw('Expected an integer but received: 3/4')
+        } catch (error) {
+          message = error.message
+        }
+        expect(message).to.equal('Expected an integer but received: 3/4')
       })
 
-      it('does not parse strings', () => {
-        expect(() => {
-          parse(['--int', 's10'], {
+      it('does not parse strings', async () => {
+        let message = ''
+        try {
+          await parse(['--int', 's10'], {
             flags: {int: flags.integer()},
           })
-        }).to.throw('Expected an integer but received: s10')
+        } catch (error) {
+          message = error.message
+        }
+        expect(message).to.equal('Expected an integer but received: s10')
       })
     })
   })
 
-  it('--no-color', () => {
-    const out = parse(['--no-color'], {})
+  it('--no-color', async () => {
+    const out = await parse(['--no-color'], {})
     expect(out.flags).to.deep.include({color: false})
   })
 
   describe('parse', () => {
-    it('parse', () => {
-      const out = parse(['--foo=bar', '100'], {
-        args: [{name: 'num', parse: i => parseInt(i, 10)}],
-        flags: {foo: flags.string({parse: input => input.toUpperCase()})},
+    it('parse', async () => {
+      const out = await parse(['--foo=bar', '100'], {
+        args: [{name: 'num', parse: async i => parseInt(i, 10)}],
+        flags: {foo: flags.string({parse: async input => input.toUpperCase()})},
       })
       expect(out.flags).to.deep.include({foo: 'BAR'})
       expect(out.args).to.deep.include({num: 100})
       expect(out.argv).to.deep.equal([100])
     })
 
-    // it('gets arg/flag in context', () => {
-    //   const out = parse({
+    // it('gets arg/flag in context', async () => {
+    //   const out = await parse({
     //     args: [{ name: 'num', parse: (_, ctx) => ctx.arg.name!.toUpperCase() }],
     //     argv: ['--foo=bar', '100'],
     //     flags: { foo: flags.string({ parse: (_, ctx) => ctx.flag.name.toUpperCase() }) },
@@ -400,16 +436,16 @@ See more help with --help`)
   })
 
   describe('flag with multiple inputs', () => {
-    it('flag multiple with flag in the middle', () => {
-      const out = parse(['--foo=bar', '--foo', '100', '--hello', 'world'], {
+    it('flag multiple with flag in the middle', async () => {
+      const out = await parse(['--foo=bar', '--foo', '100', '--hello', 'world'], {
         flags: {foo: flags.string({multiple: true}), hello: flags.string()},
       })
       expect(out.flags).to.deep.include({foo: ['bar', '100']})
       expect(out.flags).to.deep.include({hello: 'world'})
     })
 
-    it('flag multiple without flag in the middle', () => {
-      const out = parse(
+    it('flag multiple without flag in the middle', async () => {
+      const out = await parse(
         ['--foo', './a.txt', './b.txt', './c.txt', '--hello', 'world'],
         {
           flags: {
@@ -424,8 +460,8 @@ See more help with --help`)
       expect(out.flags).to.deep.include({hello: 'world'})
     })
 
-    it('flag multiple with arguments', () => {
-      const out = parse(
+    it('flag multiple with arguments', async () => {
+      const out = await parse(
         ['--foo', './a.txt', './b.txt', './c.txt', '--', '15'],
         {
           args: [{name: 'num'}],
@@ -440,8 +476,8 @@ See more help with --help`)
   })
 
   describe('defaults', () => {
-    it('generates metadata for defaults', () => {
-      const out = parse(['-n', 'heroku'], {
+    it('generates metadata for defaults', async () => {
+      const out = await parse(['-n', 'heroku'], {
         flags: {
           name: flags.string({
             char: 'n',
@@ -457,8 +493,8 @@ See more help with --help`)
       })
     })
 
-    it('defaults', () => {
-      const out = parse([], {
+    it('defaults', async () => {
+      const out = await parse([], {
         args: [{name: 'baz', default: 'BAZ'}],
         flags: {foo: flags.string({default: 'bar'})},
       })
@@ -467,27 +503,27 @@ See more help with --help`)
       expect(out.flags).to.deep.include({foo: 'bar'})
     })
 
-    it('accepts falsy', () => {
-      const out = parse([], {
+    it('accepts falsy', async () => {
+      const out = await parse([], {
         args: [{name: 'baz', default: false}],
       })
       expect(out.args).to.deep.include({baz: false})
     })
 
-    it('default as function', () => {
-      const out = parse([], {
+    it('default as function', async () => {
+      const out = await parse([], {
         args: [{name: 'baz', default: () => 'BAZ'}],
-        flags: {foo: flags.string({default: () => 'bar'})},
+        flags: {foo: flags.string({default: async () => 'bar'})},
       })
       expect(out.args).to.deep.include({baz: 'BAZ'})
       expect(out.argv).to.deep.equal(['BAZ'])
       expect(out.flags).to.deep.include({foo: 'bar'})
     })
 
-    it('default has options', () => {
-      const def: Interfaces.Default<string | undefined> = ({options}) =>
+    it('default has options', async () => {
+      const def: Interfaces.Default<string | undefined> = async ({options}) =>
         options.description
-      const out = parse([], {
+      const out = await parse([], {
         // args: [{ name: 'baz', default: () => 'BAZ' }],
         flags: {foo: flags.string({description: 'bar', default: def})},
       })
@@ -496,9 +532,9 @@ See more help with --help`)
       expect(out.flags).to.deep.include({foo: 'bar'})
     })
 
-    it('can default to a different flag', () => {
-      const def: Interfaces.Default<string | undefined> = opts => opts.flags.foo
-      const out = parse(['--foo=bar'], {
+    it('can default to a different flag', async () => {
+      const def: Interfaces.Default<string | undefined> = async opts => opts.flags.foo
+      const out = await parse(['--foo=bar'], {
         flags: {
           bar: flags.string({
             default: def,
@@ -511,8 +547,8 @@ See more help with --help`)
   })
 
   describe('boolean defaults', () => {
-    it('default is true', () => {
-      const out = parse([], {
+    it('default is true', async () => {
+      const out = await parse([], {
         flags: {
           color: flags.boolean({default: true}),
         },
@@ -520,8 +556,8 @@ See more help with --help`)
       expect(out).to.deep.include({flags: {color: true}})
     })
 
-    it('default is false', () => {
-      const out = parse([], {
+    it('default is false', async () => {
+      const out = await parse([], {
         flags: {
           color: flags.boolean({default: false}),
         },
@@ -529,17 +565,17 @@ See more help with --help`)
       expect(out).to.deep.include({flags: {color: false}})
     })
 
-    it('default as function', () => {
-      const out = parse([], {
+    it('default as function', async () => {
+      const out = await parse([], {
         flags: {
-          color: flags.boolean({default: () => true}),
+          color: flags.boolean({default: async () => true}),
         },
       })
       expect(out).to.deep.include({flags: {color: true}})
     })
 
-    it('overridden true default', () => {
-      const out = parse(['--no-color'], {
+    it('overridden true default', async () => {
+      const out = await parse(['--no-color'], {
         flags: {
           color: flags.boolean({allowNo: true, default: true}),
         },
@@ -547,8 +583,8 @@ See more help with --help`)
       expect(out).to.deep.include({flags: {color: false}})
     })
 
-    it('overridden false default', () => {
-      const out = parse(['--color'], {
+    it('overridden false default', async () => {
+      const out = await parse(['--color'], {
         flags: {
           color: flags.boolean({default: false}),
         },
@@ -558,9 +594,9 @@ See more help with --help`)
   })
 
   describe('custom option', () => {
-    it('can pass parse fn', () => {
-      const foo = flags.option({char: 'f', parse: () => 100})
-      const out = parse(['-f', 'bar'], {
+    it('can pass parse fn', async () => {
+      const foo = flags.option({char: 'f', parse: async () => 100})
+      const out = await parse(['-f', 'bar'], {
         flags: {foo},
       })
       expect(out.flags).to.deep.include({foo: 100})
@@ -568,16 +604,16 @@ See more help with --help`)
   })
 
   describe('build', () => {
-    it('can pass parse fn', () => {
-      const foo = flags.build({char: 'f', parse: () => 100})
-      const out = parse(['-f', 'bar'], {
+    it('can pass parse fn', async () => {
+      const foo = flags.build({char: 'f', parse: async () => 100})
+      const out = await parse(['-f', 'bar'], {
         flags: {foo: foo()},
       })
       expect(out.flags).to.deep.include({foo: 100})
     })
-    it('does not require parse fn', () => {
+    it('does not require parse fn', async () => {
       const foo = flags.build({char: 'f'})
-      const out = parse(['-f', 'bar'], {
+      const out = await parse(['-f', 'bar'], {
         flags: {foo: foo()},
       })
       expect(out.flags).to.deep.include({foo: 'bar'})
@@ -585,43 +621,51 @@ See more help with --help`)
   })
 
   describe('flag options', () => {
-    it('accepts valid option', () => {
-      const out = parse(['--foo', 'myotheropt'], {
+    it('accepts valid option', async () => {
+      const out = await parse(['--foo', 'myotheropt'], {
         flags: {foo: flags.string({options: ['myopt', 'myotheropt']})},
       })
       expect(out.flags.foo).to.equal('myotheropt')
     })
 
-    it('fails when invalid', () => {
-      expect(() => {
-        parse(['--foo', 'invalidopt'], {
+    it('fails when invalid', async () => {
+      let message = ''
+      try {
+        await parse(['--foo', 'invalidopt'], {
           flags: {foo: flags.string({options: ['myopt', 'myotheropt']})},
         })
-      }).to.throw('Expected --foo=invalidopt to be one of: myopt, myotheropt')
+      } catch (error) {
+        message = error.message
+      }
+      expect(message).to.equal('Expected --foo=invalidopt to be one of: myopt, myotheropt\nSee more help with --help')
     })
   })
 
   describe('arg options', () => {
-    it('accepts valid option', () => {
-      const out = parse(['myotheropt'], {
+    it('accepts valid option', async () => {
+      const out = await parse(['myotheropt'], {
         args: [{name: 'foo', options: ['myopt', 'myotheropt']}],
       })
       expect(out.args.foo).to.equal('myotheropt')
     })
 
-    it('fails when invalid', () => {
-      expect(() => {
-        parse(['invalidopt'], {
+    it('fails when invalid', async () => {
+      let message = ''
+      try {
+        await parse(['invalidopt'], {
           args: [{name: 'foo', options: ['myopt', 'myotheropt']}],
         })
-      }).to.throw('Expected invalidopt to be one of: myopt, myotheropt')
+      } catch (error) {
+        message = error.message
+      }
+      expect(message).to.equal('Expected invalidopt to be one of: myopt, myotheropt\nSee more help with --help')
     })
   })
 
   describe('env', () => {
-    it('accepts as environment variable', () => {
+    it('accepts as environment variable', async () => {
       process.env.TEST_FOO = '101'
-      const out = parse([], {
+      const out = await parse([], {
         flags: {foo: flags.string({env: 'TEST_FOO'})},
       })
       expect(out.flags.foo).to.equal('101')
@@ -630,12 +674,12 @@ See more help with --help`)
   })
 
   describe('flag context', () => {
-    it('accepts context in parse', () => {
-      const out = parse(['--foo'], {
+    it('accepts context in parse', async () => {
+      const out = await parse(['--foo'], {
         context: {a: 101},
         flags: {
           foo: flags.boolean({
-            parse: (_: any, ctx: any) => ctx.a,
+            parse: async (_: any, ctx: any) => ctx.a,
           }),
         },
       })
@@ -643,16 +687,16 @@ See more help with --help`)
     })
   })
 
-  it('parses multiple flags', () => {
-    const out = parse(['--foo=a', '--foo', 'b'], {
+  it('parses multiple flags', async () => {
+    const out = await parse(['--foo=a', '--foo', 'b'], {
       flags: {foo: flags.string()},
     })
     expect(out.flags.foo).to.equal('b')
   })
 
   describe('dependsOn', () => {
-    it('ignores', () => {
-      parse([], {
+    it('ignores', async () => {
+      await parse([], {
         flags: {
           foo: flags.string({dependsOn: ['bar']}),
           bar: flags.string({char: 'b'}),
@@ -660,8 +704,8 @@ See more help with --help`)
       })
     })
 
-    it('succeeds', () => {
-      const out = parse(['--foo', 'a', '-bb'], {
+    it('succeeds', async () => {
+      const out = await parse(['--foo', 'a', '-bb'], {
         flags: {
           foo: flags.string({dependsOn: ['bar']}),
           bar: flags.string({char: 'b'}),
@@ -671,21 +715,25 @@ See more help with --help`)
       expect(out.flags.bar).to.equal('b')
     })
 
-    it('fails', () => {
-      expect(() => {
-        parse(['--foo', 'a'], {
+    it('fails', async () => {
+      let message = ''
+      try {
+        await parse(['--foo', 'a'], {
           flags: {
             foo: flags.string({dependsOn: ['bar']}),
             bar: flags.string({char: 'b'}),
           },
         })
-      }).to.throw('--bar= must also be provided when using --foo=')
+      } catch (error) {
+        message = error.message
+      }
+      expect(message).to.equal('--bar= must also be provided when using --foo=')
     })
   })
 
   describe('exclusive', () => {
-    it('ignores', () => {
-      parse([], {
+    it('ignores', async () => {
+      await parse([], {
         flags: {
           foo: flags.string({exclusive: ['bar']}),
           bar: flags.string({char: 'b'}),
@@ -693,8 +741,8 @@ See more help with --help`)
       })
     })
 
-    it('succeeds', () => {
-      const out = parse(['--foo', 'a'], {
+    it('succeeds', async () => {
+      const out = await parse(['--foo', 'a'], {
         flags: {
           foo: flags.string({exclusive: ['bar']}),
           bar: flags.string({char: 'b'}),
@@ -703,43 +751,55 @@ See more help with --help`)
       expect(out.flags.foo).to.equal('a')
     })
 
-    it('fails', () => {
-      expect(() => {
-        parse(['--foo', 'a', '-bb'], {
+    it('fails', async () => {
+      let message = ''
+      try {
+        await parse(['--foo', 'a', '-bb'], {
           flags: {
             foo: flags.string({exclusive: ['bar']}),
             bar: flags.string({char: 'b'}),
           },
         })
-      }).to.throw('--bar= cannot also be provided when using --foo=')
+      } catch (error) {
+        message = error.message
+      }
+      expect(message).to.equal('--bar= cannot also be provided when using --foo=')
     })
   })
 
   describe('exactlyOne', () => {
-    it('throws if neither is set', () => {
-      expect(() => {
-        parse([], {
+    it('throws if neither is set', async () => {
+      let message = ''
+      try {
+        await parse([], {
           flags: {
             foo: flags.string({exactlyOne: ['bar']}),
             bar: flags.string({char: 'b', exactlyOne: ['foo']}),
           },
         })
-      }).to.throw()
+      } catch (error) {
+        message = error.message
+      }
+      expect(message).to.equal('Exactly one of the following must be provided: b,a,r')
     })
 
-    it('throws if multiple are set', () => {
-      expect(() => {
-        parse(['--foo', 'a', '--bar', 'b'], {
+    it('throws if multiple are set', async () => {
+      let message = ''
+      try {
+        await parse(['--foo', 'a', '--bar', 'b'], {
           flags: {
             foo: flags.string({exactlyOne: ['bar']}),
             bar: flags.string({char: 'b', exactlyOne: ['foo']}),
           },
         })
-      }).to.throw('--bar= cannot also be provided when using --foo=')
+      } catch (error) {
+        message = error.message
+      }
+      expect(message).to.equal('--bar= cannot also be provided when using --foo=')
     })
 
-    it('succeeds if exactly one', () => {
-      const out = parse(['--foo', 'a', '--else', '4'], {
+    it('succeeds if exactly one', async () => {
+      const out = await parse(['--foo', 'a', '--else', '4'], {
         flags: {
           foo: flags.string({exactlyOne: ['bar']}),
           bar: flags.string({char: 'b', exactlyOne: ['foo']}),
@@ -749,8 +809,8 @@ See more help with --help`)
       expect(out.flags.foo).to.equal('a')
     })
 
-    it('succeeds if exactly one (the other option)', () => {
-      const out = parse(['--bar', 'b', '--else', '4'], {
+    it('succeeds if exactly one (the other option)', async () => {
+      const out = await parse(['--bar', 'b', '--else', '4'], {
         flags: {
           foo: flags.string({exactlyOne: ['bar']}),
           bar: flags.string({char: 'b', exactlyOne: ['foo']}),
@@ -760,8 +820,8 @@ See more help with --help`)
       expect(out.flags.bar).to.equal('b')
     })
 
-    it('succeeds if exactly one of three', () => {
-      const out = parse(['--bar', 'b'], {
+    it('succeeds if exactly one of three', async () => {
+      const out = await parse(['--bar', 'b'], {
         flags: {
           foo: flags.string({exactlyOne: ['bar', 'else']}),
           bar: flags.string({char: 'b', exactlyOne: ['foo', 'else']}),
@@ -771,8 +831,8 @@ See more help with --help`)
       expect(out.flags.bar).to.equal('b')
     })
 
-    it('lets user list flag in its own list', () => {
-      const out = parse(['--bar', 'b'], {
+    it('lets user list flag in its own list', async () => {
+      const out = await parse(['--bar', 'b'], {
         flags: {
           foo: flags.string({exactlyOne: ['foo', 'bar', 'else']}),
           bar: flags.string({char: 'b', exactlyOne: ['foo', 'bar', 'else']}),
@@ -782,36 +842,49 @@ See more help with --help`)
       expect(out.flags.bar).to.equal('b')
     })
 
-    it('fails if multiple of three', () => {
-      expect(() => {
-        parse(['--foo', 'a', '--else', '4'], {
+    it('fails if multiple of three', async () => {
+      let message = ''
+      try {
+        await parse(['--foo', 'a', '--else', '4'], {
           flags: {
             foo: flags.string({exactlyOne: ['bar', 'else']}),
             bar: flags.string({char: 'b', exactlyOne: ['foo', 'else']}),
             else: flags.string({char: 'e', exactlyOne: ['foo', 'bar']}),
           },
         })
-      }).to.throw()
+      } catch (error) {
+        message = error.message
+      }
+      expect(message).to.equal('--else= cannot also be provided when using --foo=')
     })
 
-    it('handles cross-references/pairings that don\'t make sense', () => {
+    it('handles cross-references/pairings that don\'t make sense', async () => {
       const crazyFlags = {
         foo: flags.string({exactlyOne: ['bar']}),
         bar: flags.string({char: 'b', exactlyOne: ['else']}),
         else: flags.string({char: 'e'}),
       }
-      expect(() => {
-        parse(['--foo', 'a', '--bar', '4'], {
+      let message1 = ''
+      try {
+        await parse(['--foo', 'a', '--bar', '4'], {
           flags: crazyFlags,
         })
-      }).to.throw()
+      } catch (error) {
+        message1 = error.message
+      }
+      expect(message1).to.equal('--bar= cannot also be provided when using --foo=')
 
-      expect(() => {
-        parse(['--bar', 'a', '--else', '4'], {
+      let message2 = ''
+      try {
+        await parse(['--bar', 'a', '--else', '4'], {
           flags: crazyFlags,
         })
-      }).to.throw()
-      const out = parse(['--foo', 'a', '--else', '4'], {
+      } catch (error) {
+        message2 = error.message
+      }
+      expect(message2).to.equal('--else= cannot also be provided when using --bar=')
+
+      const out = await parse(['--foo', 'a', '--else', '4'], {
         flags: crazyFlags,
       })
       expect(out.flags.foo).to.equal('a')
@@ -821,24 +894,24 @@ See more help with --help`)
   })
 
   describe('allowNo', () => {
-    it('is undefined if not set', () => {
-      const out = parse([], {
+    it('is undefined if not set', async () => {
+      const out = await parse([], {
         flags: {
           foo: flags.boolean({allowNo: true}),
         },
       })
       expect(out.flags.foo).to.equal(undefined)
     })
-    it('is false', () => {
-      const out = parse(['--no-foo'], {
+    it('is false', async () => {
+      const out = await parse(['--no-foo'], {
         flags: {
           foo: flags.boolean({allowNo: true}),
         },
       })
       expect(out.flags.foo).to.equal(false)
     })
-    it('is true', () => {
-      const out = parse(['--foo'], {
+    it('is true', async () => {
+      const out = await parse(['--foo'], {
         flags: {
           foo: flags.boolean({allowNo: true}),
         },