Skip to content

Commit 2dae2bf

Browse files
authoredJul 7, 2019
Implement --config flag
Fixes #1857.
1 parent 58b2350 commit 2dae2bf

File tree

10 files changed

+116
-31
lines changed

10 files changed

+116
-31
lines changed
 

‎docs/05-command-line.md

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ $ npx ava --help
2121
--color Force color output
2222
--no-color Disable color output
2323
--reset-cache Reset AVA's compilation cache and exit
24+
--config JavaScript file for AVA to read its config from, instead of using package.json
25+
or ava.config.js files
2426

2527
Examples
2628
ava

‎docs/06-configuration.md

+32-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Translations: [Français](https://github.com/avajs/ava-docs/blob/master/fr_FR/docs/06-configuration.md)
44

5-
All of the [CLI options](./05-command-line.md) can be configured in the `ava` section of either your `package.json` file, or an `ava.config.js` file. This allows you to modify the default behavior of the `ava` command, so you don't have to repeatedly type the same options on the command prompt.
5+
All of the [CLI options][CLI] can be configured in the `ava` section of either your `package.json` file, or an `ava.config.js` file. This allows you to modify the default behavior of the `ava` command, so you don't have to repeatedly type the same options on the command prompt.
66

77
To ignore files, prefix the pattern with an `!` (exclamation mark).
88

@@ -115,6 +115,35 @@ export default ({projectDir}) => {
115115

116116
Note that the final configuration must not be a promise.
117117

118+
## Alternative configuration files
119+
120+
The [CLI] lets you specify a specific configuration file, using the `--config` flag. This file is processed just like an `ava.config.js` file would be. When the `--config` flag is set, the provided file will override all configuration from the `package.json` and `ava.config.js` files. The configuration is not merged.
121+
122+
The configuration file *must* be in the same directory as the `package.json` file.
123+
124+
You can use this to customize configuration for a specific test run. For instance, you may want to run unit tests separately from integration tests:
125+
126+
`ava.config.js`:
127+
128+
```js
129+
export default {
130+
files: ['unit-tests/**/*']
131+
};
132+
```
133+
134+
`integration-tests.config.js`:
135+
136+
```js
137+
import baseConfig from './ava.config.js';
138+
139+
export default {
140+
...baseConfig,
141+
files: ['integration-tests/**/*']
142+
};
143+
```
144+
145+
You can now run your unit tests through `npx ava` and the integration tests through `npx ava --config integration-tests.config.js`.
146+
118147
## Object printing depth
119148

120149
By default, AVA prints nested objects to a depth of `3`. However, when debugging tests with deeply nested objects, it can be useful to print with more detail. This can be done by setting [`util.inspect.defaultOptions.depth`](https://nodejs.org/api/util.html#util_util_inspect_defaultoptions) to the desired depth, before the test is executed:
@@ -132,3 +161,5 @@ test('My test', t => {
132161
```
133162

134163
AVA has a minimum depth of `3`.
164+
165+
[CLI]: ./05-command-line.md

‎eslint-plugin-helper.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function load(projectDir, overrides) {
1818
if (configCache.has(projectDir)) {
1919
({conf, babelConfig} = configCache.get(projectDir));
2020
} else {
21-
conf = loadConfig(projectDir);
21+
conf = loadConfig({resolveFrom: projectDir});
2222
babelConfig = babelPipeline.validate(conf.babel);
2323
configCache.set(projectDir, {conf, babelConfig});
2424
}

‎lib/cli.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const arrify = require('arrify');
77
const meow = require('meow');
88
const Promise = require('bluebird');
99
const isCi = require('is-ci');
10-
const loadConf = require('./load-config');
10+
const loadConfig = require('./load-config');
1111

1212
// Bluebird specific
1313
Promise.longStackTraces();
@@ -18,10 +18,17 @@ function exit(message) {
1818
}
1919

2020
exports.run = async () => { // eslint-disable-line complexity
21+
const {flags: {config: configFile}} = meow({ // Process the --config flag first
22+
autoHelp: false, // --help should get picked up by the next meow invocation.
23+
flags: {
24+
config: {type: 'string'}
25+
}
26+
});
27+
2128
let conf = {};
2229
let confError = null;
2330
try {
24-
conf = loadConf();
31+
conf = loadConfig({configFile});
2532
} catch (error) {
2633
confError = error;
2734
}
@@ -43,6 +50,8 @@ exports.run = async () => { // eslint-disable-line complexity
4350
--color Force color output
4451
--no-color Disable color output
4552
--reset-cache Reset AVA's compilation cache and exit
53+
--config JavaScript file for AVA to read its config from, instead of using package.json
54+
or ava.config.js files
4655
4756
Examples
4857
ava

‎lib/load-config.js

+26-12
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,23 @@ const pkgConf = require('pkg-conf');
77
const NO_SUCH_FILE = Symbol('no ava.config.js file');
88
const MISSING_DEFAULT_EXPORT = Symbol('missing default export');
99

10-
function loadConfig(resolveFrom = process.cwd(), defaults = {}) {
11-
const packageConf = pkgConf.sync('ava', {cwd: resolveFrom});
10+
function loadConfig({configFile, resolveFrom = process.cwd(), defaults = {}} = {}) { // eslint-disable-line complexity
11+
let packageConf = pkgConf.sync('ava', {cwd: resolveFrom});
1212
const filepath = pkgConf.filepath(packageConf);
1313
const projectDir = filepath === null ? resolveFrom : path.dirname(filepath);
1414

15+
const fileForErrorMessage = configFile || 'ava.config.js';
16+
const allowConflictWithPackageJson = Boolean(configFile);
17+
18+
if (configFile) {
19+
configFile = path.resolve(configFile); // Relative to CWD
20+
if (path.basename(configFile) !== path.relative(projectDir, configFile)) {
21+
throw new Error('Config files must be located next to the package.json file');
22+
}
23+
} else {
24+
configFile = path.join(projectDir, 'ava.config.js');
25+
}
26+
1527
let fileConf;
1628
try {
1729
({default: fileConf = MISSING_DEFAULT_EXPORT} = esm(module, {
@@ -26,45 +38,47 @@ function loadConfig(resolveFrom = process.cwd(), defaults = {}) {
2638
},
2739
force: true,
2840
mode: 'all'
29-
})(path.join(projectDir, 'ava.config.js')));
41+
})(configFile));
3042
} catch (error) {
3143
if (error && error.code === 'MODULE_NOT_FOUND') {
3244
fileConf = NO_SUCH_FILE;
3345
} else {
34-
throw Object.assign(new Error('Error loading ava.config.js'), {parent: error});
46+
throw Object.assign(new Error(`Error loading ${fileForErrorMessage}`), {parent: error});
3547
}
3648
}
3749

3850
if (fileConf === MISSING_DEFAULT_EXPORT) {
39-
throw new Error('ava.config.js must have a default export, using ES module syntax');
51+
throw new Error(`${fileForErrorMessage} must have a default export, using ES module syntax`);
4052
}
4153

4254
if (fileConf !== NO_SUCH_FILE) {
43-
if (Object.keys(packageConf).length > 0) {
44-
throw new Error('Conflicting configuration in ava.config.js and package.json');
55+
if (allowConflictWithPackageJson) {
56+
packageConf = {};
57+
} else if (Object.keys(packageConf).length > 0) {
58+
throw new Error(`Conflicting configuration in ${fileForErrorMessage} and package.json`);
4559
}
4660

4761
if (fileConf && typeof fileConf.then === 'function') { // eslint-disable-line promise/prefer-await-to-then
48-
throw new TypeError('ava.config.js must not export a promise');
62+
throw new TypeError(`${fileForErrorMessage} must not export a promise`);
4963
}
5064

5165
if (!isPlainObject(fileConf) && typeof fileConf !== 'function') {
52-
throw new TypeError('ava.config.js must export a plain object or factory function');
66+
throw new TypeError(`${fileForErrorMessage} must export a plain object or factory function`);
5367
}
5468

5569
if (typeof fileConf === 'function') {
5670
fileConf = fileConf({projectDir});
5771
if (fileConf && typeof fileConf.then === 'function') { // eslint-disable-line promise/prefer-await-to-then
58-
throw new TypeError('Factory method exported by ava.config.js must not return a promise');
72+
throw new TypeError(`Factory method exported by ${fileForErrorMessage} must not return a promise`);
5973
}
6074

6175
if (!isPlainObject(fileConf)) {
62-
throw new TypeError('Factory method exported by ava.config.js must return a plain object');
76+
throw new TypeError(`Factory method exported by ${fileForErrorMessage} must return a plain object`);
6377
}
6478
}
6579

6680
if ('ava' in fileConf) {
67-
throw new Error('Encountered \'ava\' property in ava.config.js; avoid wrapping the configuration');
81+
throw new Error(`Encountered 'ava' property in ${fileForErrorMessage}; avoid wrapping the configuration`);
6882
}
6983
}
7084

‎profile.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ function resolveModules(modules) {
3232

3333
Promise.longStackTraces();
3434

35-
const conf = loadConfig(undefined, {
36-
babel: {
37-
testOptions: {}
38-
},
39-
compileEnhancements: true
35+
const conf = loadConfig({
36+
defaults: {
37+
babel: {
38+
testOptions: {}
39+
},
40+
compileEnhancements: true
41+
}
4042
});
4143

4244
const {projectDir} = conf;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
files: 'package-yes-explicit-yes-test-value'
3+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
files: 'package-yes-explicit-yes-nested-test-value'
3+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"ava": {
3+
"files": [
4+
"abc",
5+
"!xyz"
6+
]
7+
}
8+
}

‎test/load-config.js

+23-10
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,27 @@ test('finds config in package.json', t => {
2323

2424
test('loads config from a particular directory', t => {
2525
changeDir('throws');
26-
const conf = loadConfig(path.resolve(__dirname, 'fixture', 'load-config', 'package-only'));
26+
const conf = loadConfig({resolveFrom: path.resolve(__dirname, 'fixture', 'load-config', 'package-only')});
2727
t.is(conf.failFast, true);
2828
t.end();
2929
});
3030

31-
test('throws a warning of both configs are present', t => {
31+
test('throws a warning if both configs are present', t => {
3232
changeDir('package-yes-file-yes');
33-
t.throws(loadConfig);
33+
t.throws(loadConfig, /Conflicting configuration in ava.config.js and package.json/);
34+
t.end();
35+
});
36+
37+
test('explicit configFile option overrides package.json config', t => {
38+
changeDir('package-yes-explicit-yes');
39+
const {files} = loadConfig({configFile: 'explicit.js'});
40+
t.is(files, 'package-yes-explicit-yes-test-value');
41+
t.end();
42+
});
43+
44+
test('throws if configFile option is not in the same directory as the package.json file', t => {
45+
changeDir('package-yes-explicit-yes');
46+
t.throws(() => loadConfig({configFile: 'nested/explicit.js'}), /Config files must be located next to the package.json file/);
3447
t.end();
3548
});
3649

@@ -39,7 +52,7 @@ test('merges in defaults passed with initial call', t => {
3952
const defaults = {
4053
files: ['123', '!456']
4154
};
42-
const {files, failFast} = loadConfig(undefined, defaults);
55+
const {files, failFast} = loadConfig({defaults});
4356
t.is(failFast, true, 'preserves original props');
4457
t.is(files, defaults.files, 'merges in extra props');
4558
t.end();
@@ -68,25 +81,25 @@ test('supports require() inside config file', t => {
6881

6982
test('throws an error if a config factory returns a promise', t => {
7083
changeDir('factory-no-promise-return');
71-
t.throws(loadConfig);
84+
t.throws(loadConfig, /Factory method exported by ava.config.js must not return a promise/);
7285
t.end();
7386
});
7487

7588
test('throws an error if a config exports a promise', t => {
7689
changeDir('no-promise-config');
77-
t.throws(loadConfig);
90+
t.throws(loadConfig, /ava.config.js must not export a promise/);
7891
t.end();
7992
});
8093

8194
test('throws an error if a config factory does not return a plain object', t => {
8295
changeDir('factory-no-plain-return');
83-
t.throws(loadConfig);
96+
t.throws(loadConfig, /Factory method exported by ava.config.js must return a plain object/);
8497
t.end();
8598
});
8699

87100
test('throws an error if a config does not export a plain object', t => {
88101
changeDir('no-plain-config');
89-
t.throws(loadConfig);
102+
t.throws(loadConfig, /ava.config.js must export a plain object or factory function/);
90103
t.end();
91104
});
92105

@@ -109,12 +122,12 @@ test('rethrows wrapped module errors', t => {
109122

110123
test('throws an error if a config file has no default export', t => {
111124
changeDir('no-default-export');
112-
t.throws(loadConfig);
125+
t.throws(loadConfig, /ava.config.js must have a default export, using ES module syntax/);
113126
t.end();
114127
});
115128

116129
test('throws an error if a config file contains `ava` property', t => {
117130
changeDir('contains-ava-property');
118-
t.throws(loadConfig);
131+
t.throws(loadConfig, /Encountered 'ava' property in ava.config.js; avoid wrapping the configuration/);
119132
t.end();
120133
});

0 commit comments

Comments
 (0)
Please sign in to comment.