Repository: oclif/core
Pull request #474 (2022-08-16 20:23:31)

fix: parser doesn't validate against options parameter if the value is provided through an env var
Hey, I found this issue that when I try to pass a flag as an environment variable it is not validated if this value is on the options field.

An example would be

```javascript
export default class MyCommand extends Command {
  static flags = {
    foo: Flags.string({
      env: 'FOO',
      options: ['valid1', 'valid2']
    })
  }
}
```

My understanding here is that if I execute the command with the `FOO` environment variable as `invalid` the parsing should fail and an error message should be displayed, this is not happening.

I added a couple of tests that verify this behavior and fixed it on this PR.


salesforce-cla[bot]: Thanks for the contribution!  Before we can merge this, we need @rafaelmotaalves to [sign the Salesforce.com Contributor License Agreement](https://cla.salesforce.com/sign-cla?prUrl=https%3A%2F%2Fgithub.com%2Foclif%2Fcore%2Fpull%2F474).

Patch:
diff --git a/src/parser/parse.ts b/src/parser/parse.ts
index c17259c..e385f08 100644
--- a/src/parser/parse.ts
+++ b/src/parser/parse.ts
@@ -181,9 +181,7 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
         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)
-        }
+        this._validateOptions(flag, input)
 
         // eslint-disable-next-line no-await-in-loop
         const value = flag.parse ? await flag.parse(input, this.context) : input
@@ -201,8 +199,12 @@ 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]
-        // eslint-disable-next-line no-await-in-loop
-        if (input) flags[k] = await flag.parse(input, this.context)
+        if (input) {
+          this._validateOptions(flag, input)
+
+          // eslint-disable-next-line no-await-in-loop
+          flags[k] = await flag.parse(input, this.context)
+        }
       }
 
       if (!(k in flags) && flag.default !== undefined) {
@@ -216,6 +218,11 @@ export class Parser<T extends ParserInput, TFlags extends OutputFlags<T['flags']
     return flags
   }
 
+  private _validateOptions(flag: OptionFlag<any>, input: string) {
+    if (flag.options && !flag.options.includes(input))
+      throw new m.errors.FlagInvalidOptionError(flag, input)
+  }
+
   private async _argv(): Promise<any[]> {
     const args: any[] = []
     const tokens = this._argTokens


Test patch:
diff --git a/test/parser/parse.test.ts b/test/parser/parse.test.ts
index 0f8e813..86266af 100644
--- a/test/parser/parse.test.ts
+++ b/test/parser/parse.test.ts
@@ -790,6 +790,28 @@ See more help with --help`)
 
       expect(message).to.equal('Expected --foo=invalidopt to be one of: myopt, myotheropt\nSee more help with --help')
     })
+    it('fails when invalid env var', async () => {
+      let message = ''
+      process.env.TEST_FOO = 'invalidopt'
+      try {
+        await parse([], {
+          flags: {foo: flags.string({options: ['myopt', 'myotheropt'], env: 'TEST_FOO'})},
+        })
+      } catch (error: any) {
+        message = error.message
+      }
+
+      expect(message).to.equal('Expected --foo=invalidopt to be one of: myopt, myotheropt\nSee more help with --help')
+    })
+
+    it('accepts valid option env var', async () => {
+      process.env.TEST_FOO = 'myopt'
+
+      const out = await parse([], {
+        flags: {foo: flags.string({options: ['myopt', 'myotheropt'], env: 'TEST_FOO'})},
+      })
+      expect(out.flags.foo).to.equal('myopt')
+    })
   })
 
   describe('url flag', () => {