Skip to content

Commit

Permalink
Evaluate ava.config.js scripts in current context
Browse files Browse the repository at this point in the history
Fixes #2405.
  • Loading branch information
novemberborn committed Feb 23, 2020
1 parent 8831f54 commit f4d4edd
Show file tree
Hide file tree
Showing 3 changed files with 6 additions and 36 deletions.
37 changes: 4 additions & 33 deletions lib/load-config.js
Expand Up @@ -9,40 +9,15 @@ const NO_SUCH_FILE = Symbol('no ava.config.js file');
const MISSING_DEFAULT_EXPORT = Symbol('missing default export');
const EXPERIMENTS = new Set(['tryAssertion']);

class LegacyCommonJsAccessError extends Error {
constructor(thing, fileForErrorMessage) {
super(`${thing} is not available in ${fileForErrorMessage}. Use a .cjs file instead`);
this.name = 'LegacyCommonJsAccessError';
}
}

// *Very* rudimentary support for loading ava.config.js files containing an `export default` statement.
const evaluateJsConfig = (configFile, fileForErrorMessage) => {
const evaluateJsConfig = configFile => {
const contents = fs.readFileSync(configFile, 'utf8');
const script = new vm.Script(`'use strict';(()=>{let __export__;\n${contents.replace(/export default/g, '__export__ =')};return __export__;})()`, {
filename: configFile,
lineOffset: -1
});
return {
default: script.runInNewContext({
console,
process,
get __dirname() {
throw new LegacyCommonJsAccessError('__dirname', fileForErrorMessage);
},
get __filename() {
throw new LegacyCommonJsAccessError('__filename', fileForErrorMessage);
},
get module() {
throw new LegacyCommonJsAccessError('module', fileForErrorMessage);
},
get exports() {
throw new LegacyCommonJsAccessError('exports', fileForErrorMessage);
},
get require() {
throw new LegacyCommonJsAccessError('require()', fileForErrorMessage);
}
})
default: script.runInThisContext()
};
};

Expand All @@ -55,17 +30,13 @@ const loadJsConfig = ({projectDir, configFile = path.join(projectDir, 'ava.confi

let config;
try {
({default: config = MISSING_DEFAULT_EXPORT} = evaluateJsConfig(configFile, fileForErrorMessage));
({default: config = MISSING_DEFAULT_EXPORT} = evaluateJsConfig(configFile));
} catch (error) {
if (error.code === 'ENOENT') {
return null;
}

if (error.name === 'LegacyCommonJsAccessError') {
throw error;
} else {
throw Object.assign(new Error(`Error loading ${fileForErrorMessage}`), {parent: error});
}
throw Object.assign(new Error(`Error loading ${fileForErrorMessage}: ${error.message}`), {parent: error});
}

if (config === MISSING_DEFAULT_EXPORT) {
Expand Down
3 changes: 1 addition & 2 deletions test/integration/config.js
Expand Up @@ -3,7 +3,6 @@ const fs = require('fs');
const path = require('path');
const {test} = require('tap');
const execa = require('execa');
const figures = require('figures');
const tempy = require('tempy');
const {execCli} = require('../helper/cli');

Expand All @@ -13,7 +12,7 @@ test('formats errors from ava.config.js', t => {

const lines = stderr.split('\n');
t.is(lines[0], '');
t.is(lines[1], ' ' + figures.cross + ' Error loading ava.config.js');
t.match(lines[1], /Error loading ava\.config\.js:/);
t.is(lines[2], '');
t.match(lines[3], /ava\.config\.js/);
t.match(lines[4], /foo/);
Expand Down
2 changes: 1 addition & 1 deletion test/load-config.js
Expand Up @@ -80,7 +80,7 @@ test('loads config from factory function', t => {

test('does not support require() inside config.js files', t => {
changeDir('require');
t.throws(loadConfig, /require\(\) is not available in ava\.config\.js\. Use a \.cjs file instead/);
t.throws(loadConfig, /Error loading ava\.config\.js: require is not defined/);
t.end();
});

Expand Down

0 comments on commit f4d4edd

Please sign in to comment.