Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for ava.config.js files #1761

Merged
merged 48 commits into from
May 30, 2018
Merged

Conversation

good-idea
Copy link
Contributor

@good-idea good-idea commented Apr 2, 2018

Fixes #520

  • When invoked, AVA walks the file hierarchy looking for a package.json file
  • AVA checks whether a sibling ava.config.js file exists
  • If the package.json file contains an "ava" configuration, and ava.config.js exists, AVA will exit with a warning about ambiguous configuration
  • Otherwise the package.json configuration takes precedence over ava.config.js
  • ava.config.js is loaded through esm so can be either a CJS or ESM file. The loader should support ESM transpilation by looking for a thruthy __esModule property: if set then the value of the default property is used
  • An actual configuration object may be exported, or a factory function
  • If a factory function is exported it'll be called with the project directory (for future compatibility). It must return a configuration object
  • The configuration object must not have a then property
  • (These are the same rules as for .babelrc.js files in Babel 7)
  • Smart JS/JSON filetype in babel-config.js
  • Update docs
  • Explore esm options so we don't have to support the module.exports workarounds etc

@pvdlg
Copy link

pvdlg commented Apr 3, 2018

Have you checked cosmiconfig? It would fulfill a lot of the requirements, and the one missing might be implement over there.

@novemberborn
Copy link
Member

We'd end up using very little of cosmiconfig. It's relatively straight-forward to hook this up directly in AVA.

@good-idea thanks for your PR. I've been quite busy lately so I make need another few days before I can get back to you.

@good-idea
Copy link
Contributor Author

@novemberborn No hurry, I don't think I'll be able to chip away at this until the end of the week.

This is my first contribution to OSS 🤓 so any feedback is welcome!

@novemberborn
Copy link
Member

Some quick responses to your questions (I'll try and get to the other stuff later):

This is still using pkg-conf under the hood. But, we could easily remove it as a dependency, then use load-json-file instead. Any reasons for/against?

No strong feelings either way, though I suppose I should look into what pkg-conf does exactly, and I haven't done that yet. When in doubt, lean toward making fewer changes 😉

I've made some dirs in test/fixture/load-config that I'll use in writing more tests. But I'm not sure how to test cases when there is no package.json, because the test will travel up to the root ava dir and see its package.json. (The current behavior is to keep searching up until /, then finally return false if nothing is found)

No that's fine. The test case we're looking for is where there are package.json files, both with and without an "ava" configuration.

Some tests are failing, but they don't seem to be related

A newer Babel 7 is being picked up in one of the test jobs, and it's causing test failures. I'm a little surprised at that, to be honest! Feel free to ignore the Travis run with FRESH_DEPS=true, and AppVeyor as well since it doesn't run any other tests after the fresh-deps one fails.

Copy link
Member

@novemberborn novemberborn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great start @good-idea! There seems to be some confusion around where the ava.config.js file is supposed to be. It's either next to a package.json file or in the current directory. I don't want it to be a secondary way for AVA to determine the project directory. If this proves too confusing we can change it, but for now I like the simplicity of AVA determining what is a "project" based on package.json files only.

const findConfigFiles = () => {
const packageFile = findPackageDotJson();
const configFile = findConfigFile(
packageFile ? path.dirname(packageFile) : process.cwd(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's simplify this. We can keep the current pkg-conf logic to find the package.json, if any. Afterwards, the ava.config.js file must be in the projectDir. We won't walk the file system to find it. (This means the findFileUp logic above is not necessary.)

test/cli.js Outdated
@@ -40,7 +40,9 @@ function execCli(args, opts, cb) {

child.on('close', (code, signal) => {
if (code) {
const err = new Error(`test-worker exited with a non-zero exit code: ${code}`);
const err = new Error(
`test-worker exited with a non-zero exit code: ${code}`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you're using Prettier. Could you exclude unrelated changes from the PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it!

@novemberborn
Copy link
Member

@good-idea if you could merge in (or rebase on) master, that should fix some of the CI issues.

@good-idea
Copy link
Contributor Author

@novemberborn thanks, I'll working on this later today!

@good-idea
Copy link
Contributor Author

@novemberborn I think this just about does it, let me know if you see anything else that should change!

Copy link
Member

@novemberborn novemberborn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@good-idea this is looking great!

I haven't actually played with the implementation yet, and I must admit I haven't really looked at the tests just yet either. There's a few things that should be addressed (see inline comments).

Looking at the diff on GitHub it seems to want to add things that are already in master. Not quite sure what's happening. Perhaps try merging in master or rebasing 😄

I'm very excited about landing this!

'use strict';
const path = require('path');
const fs = require('fs');
// ESM seems to break `path` and `fs`, require them first before setting it up..
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

esm shouldn't impact require() calls though. Happy to see the Node.js builtins loaded first, but your comment hints at an esm bug. Is this still happening for you?

Copy link
Contributor Author

@good-idea good-idea Apr 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked into why, but if I set up ESM first like so:

'use strict';
require = require('esm')(module);
const path = require('path');
console.log(path);

path is null.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works for me, Node.js 8.11.0, and esm@3.0.22.

Copy link
Contributor Author

@good-idea good-idea Apr 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

esm@3.0.15 was installed, I updated to 3.0.22 and it now works as expected.
I also moved esm from devDependencies to dependencies, since it's now being used in the main code -- does this make sense?


const loadConfigFile = projectDir => {
const loaded = requireESM(path.resolve(projectDir, 'ava.config.js'));
const config = (loaded.__esModule) ? loaded.default : loaded;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does esm take care of this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, it doesn't seem to. There's a test for this that fails if you don't do the check for __esModule.

if (config.then) {
throw new Error('Config file must not return a promise');
}
return (typeof config === 'function') ? config(projectDir) : config;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's pass {projectDir}, so we can extend this with other options later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I meant doing config({projectDir}), not loadConfigFile({projectDir}).

projectDir
};

return Object.assign(config, defaults, {_meta});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This copies the defaults into config. The _meta bit seems unnecessary to me.

Better would be Object.assign({}, defaults, config, {projectDir}).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

profile.js Outdated
@@ -38,9 +38,9 @@ const conf = pkgConf.sync('ava', {
}
});

const filepath = pkgConf.filepath(conf);
const projectDir = filepath === null ? process.cwd() : path.dirname(filepath);
console.log(conf);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to remove 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@novemberborn
Copy link
Member

Oh, one more thing. The fileType values in

fileType: 'JSON',
need to change to 'JS' when the config originates from a .js file. I'm happy to fix and test that myself though.

@good-idea
Copy link
Contributor Author

@novemberborn This last update should address the notes in your review -- I didn't look into the fileType issue in babel-conifg yet, though. I updated the initial PR description to include a todo for this.

@novemberborn
Copy link
Member

@good-idea fantastic. I probably won't be able to look at this until the weekend, but thank you for your hard work on this 👍

@@ -0,0 +1,33 @@
'use strict';
const requireEsm = require('esm')(module); // eslint-disable-line no-global-assign
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think think // eslint-disable-line no-global-assign is needed.

Copy link
Member

@novemberborn novemberborn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left some comments below.

I'll have a look at the fileType: 'JS thing.

I also want to see if we can restrict this to supporting just export default / ESM.


const loadConfigFile = projectDir => {
const loaded = requireEsm(path.resolve(projectDir, 'ava.config.js'));
const config = (loaded.__esModule) ? loaded.default : loaded;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to play around with the esm options. I think we can make it always treat these files as ESM, so we don't have to support the module.exports workarounds etc. Would be neat to start with that. What do you think @sindresorhus?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if (config.then) {
throw new Error('Config file must not return a promise');
}
return (typeof config === 'function') ? config({projectDir}) : config;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The config({projectDir}) return value also must not be a promise. Happy to fix this up myself.

readme.md Outdated
};
```

The config file may also export a factory function, which will be called with an object containing a `projectDir` property. This function must return a config object, and cannot be a promise.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps:

This function must return a config object, it mustn't return a promise.

readme.md Outdated

```js
const config = ({projectDir}) => {
if (projectDir === '...') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

projectDir will be an absolute path, so it'd be good if the example didn't show dots which imply it's somehow relative.

Providing the directory is useful if you have share AVA config amongst projects, e.g. in a work setting or a monorepo. You could then differentiate between projects when necessary. Perhaps this is something we should cover in a recipe, outside of this PR.

readme.md Outdated
export default config;
```

Or, return different configuration based on the environment: `NODE_ENV=development ava`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about advocating AVA to be run with different NODE_ENV settings. It'd be more interesting to detect CI environments — although at that point we should forward the detection to the function. Perhaps remove for now and track in a follow-up issue?

@good-idea
Copy link
Contributor Author

@novemberborn I made some changes to address the above -- there another big diff with package-lock.json, so I did the same as before. Let me know if everything is looking OK.

@novemberborn
Copy link
Member

@good-idea I haven't had a chance yet to look into this. I just realized we need some changes with Babel (#1789) so I might try and land that before doing my work here.

Anyway this is super close so once that unblocks we should be good to go.

@novemberborn novemberborn changed the title [WIP] Adds support for ava.config.js files Adds support for ava.config.js files May 20, 2018
@novemberborn
Copy link
Member

@good-idea just wanted to say I haven't forgotten about this 😄 #1789 is pretty much done so I'm hoping to land that next weekend. I'll then try and get this in as well before cutting a release.

@good-idea
Copy link
Contributor Author

@novemberborn No hurry on my end! I've been using my own fork for now :)
Looking forward to the next release

@novemberborn
Copy link
Member

@good-idea I've merged latest master and pushed documentation improvements and some cleanup of the config loading. We're also now using just ESM.

There's a few scenarios that are lacking tests. Once I've done that I'll land this 🎉

@novemberborn novemberborn merged commit 16f4742 into avajs:master May 30, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants