Repository: mochajs/mocha

Problem statement:
🚀 Feature: Support setting options via environment vars
As discussed in #4232, it can be difficult to pass mocha command-line options through `npm run` scripts.  As @boneskull said:

> yargs has a feature that supports setting options via an environment variable; we should probably take advantage of that at some point. Might even be an issue for it already.

I didn't see any open issues for this so figured I would open one.


Hints:
sorry, butt-closed this one

yargs has support for this via `.env()` which we _should_ be able to leverage. 
From reading [the `yargs` docs](https://yargs.js.org/docs/#api-envprefix) seems like `.env('MOCHA')` should be sufficient, I'll open a PR soon
in my experience, it's never that easy, but I wish you luck regardless 😅 
*You know*, this just makes me wish yargs itself would accept an environment variable that's equivalent to `.env('MOCHA')` so I could do this kind of thing with any command built with yargs 
https://github.com/mochajs/mocha/pull/4835

Patch:
diff --git a/lib/cli/options.js b/lib/cli/options.js
--- a/lib/cli/options.js
+++ b/lib/cli/options.js
@@ -208,9 +208,10 @@ module.exports.loadPkgRc = loadPkgRc;
  * Priority list:
  *
  * 1. Command-line args
- * 2. RC file (`.mocharc.c?js`, `.mocharc.ya?ml`, `mocharc.json`)
- * 3. `mocha` prop of `package.json`
- * 4. default configuration (`lib/mocharc.json`)
+ * 2. `MOCHA_OPTIONS` environment variable.
+ * 3. RC file (`.mocharc.c?js`, `.mocharc.ya?ml`, `mocharc.json`)
+ * 4. `mocha` prop of `package.json`
+ * 5. default configuration (`lib/mocharc.json`)
  *
  * If a {@link module:lib/cli/one-and-dones.ONE_AND_DONE_ARGS "one-and-done" option} is present in the `argv` array, no external config files will be read.
  * @summary Parses options read from `.mocharc.*` and `package.json`.
@@ -231,6 +232,7 @@ const loadOptions = (argv = []) => {
     return args;
   }
 
+  const envConfig = parse(process.env.MOCHA_OPTIONS || '');
   const rcConfig = loadRc(args);
   const pkgConfig = loadPkgRc(args);
 
@@ -243,7 +245,14 @@ const loadOptions = (argv = []) => {
     args._ = args._.concat(pkgConfig._ || []);
   }
 
-  args = parse(args._, mocharc, args, rcConfig || {}, pkgConfig || {});
+  args = parse(
+    args._,
+    mocharc,
+    args,
+    envConfig,
+    rcConfig || {},
+    pkgConfig || {}
+  );
 
   // recombine positional arguments and "spec"
   if (args.spec) {


Test patch:
diff --git a/test/node-unit/cli/options.spec.js b/test/node-unit/cli/options.spec.js
--- a/test/node-unit/cli/options.spec.js
+++ b/test/node-unit/cli/options.spec.js
@@ -42,9 +42,10 @@ describe('options', function () {
   /**
    * Order of priority:
    * 1. Command-line args
-   * 2. RC file (`.mocharc.js`, `.mocharc.ya?ml`, `mocharc.json`)
-   * 3. `mocha` prop of `package.json`
-   * 4. default rc
+   * 2. `MOCHA_OPTIONS` environment variable
+   * 3. RC file (`.mocharc.js`, `.mocharc.ya?ml`, `mocharc.json`)
+   * 4. `mocha` prop of `package.json`
+   * 5. default rc
    */
   describe('loadOptions()', function () {
     describe('when no parameter provided', function () {
@@ -408,6 +409,30 @@ describe('options', function () {
       });
     });
 
+    describe('env options', function () {
+      it('should parse flags from MOCHA_OPTIONS', function () {
+        readFileSync = sinon.stub().onFirstCall().returns('{}');
+        findConfig = sinon.stub().returns('/some/.mocharc.json');
+        loadConfig = sinon.stub().returns({});
+        findupSync = sinon.stub().returns('/some/package.json');
+        sinon
+          .stub(process, 'env')
+          .value({MOCHA_OPTIONS: '--retries 42 --color'});
+
+        loadOptions = proxyLoadOptions({
+          readFileSync,
+          findConfig,
+          loadConfig,
+          findupSync
+        });
+
+        expect(loadOptions(), 'to satisfy', {
+          retries: 42,
+          color: true
+        });
+      });
+    });
+
     describe('config priority', function () {
       it('should prioritize package.json over defaults', function () {
         readFileSync = sinon.stub();
@@ -474,6 +499,27 @@ describe('options', function () {
           '500'
         );
       });
+
+      it('should prioritize env over rc file', function () {
+        readFileSync = sinon.stub();
+        readFileSync.onFirstCall().returns('{}');
+        readFileSync.onSecondCall().returns('');
+        findConfig = sinon.stub().returns('/some/.mocharc.json');
+        loadConfig = sinon.stub().returns({retries: 300});
+        findupSync = sinon.stub().returns('/some/package.json');
+        sinon
+          .stub(process, 'env')
+          .value({MOCHA_OPTIONS: '--retries 800 --color'});
+
+        loadOptions = proxyLoadOptions({
+          readFileSync,
+          findConfig,
+          loadConfig,
+          findupSync
+        });
+
+        expect(loadOptions(), 'to have property', 'retries', 800);
+      });
     });
 
     describe('when called with a one-and-done arg', function () {