aboutsummaryrefslogtreecommitdiff
path: root/node_modules/ava/lib/babel-config.js
blob: d58b700b893858fd8f72a6b928517be969f620ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
'use strict';
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const figures = require('figures');
const configManager = require('hullabaloo-config-manager');
const md5Hex = require('md5-hex');
const makeDir = require('make-dir');
const semver = require('semver');
const colors = require('./colors');

function validate(conf) {
	if (conf === undefined || conf === null) {
		conf = 'default';
	}

	// Check for valid babel config shortcuts (can be either `default` or `inherit`)
	const isValidShortcut = conf === 'default' || conf === 'inherit';

	if (!conf || (typeof conf === 'string' && !isValidShortcut)) {
		let message = colors.error(figures.cross);
		message += ' Unexpected Babel configuration for AVA. ';
		message += 'See ' + chalk.underline('https://github.com/avajs/ava#es2017-support') + ' for allowed values.';

		throw new Error(message);
	}

	return conf;
}

const SOURCE = '(AVA) Base Babel config';
const AVA_DIR = path.join(__dirname, '..');

function verifyExistingOptions(verifierFile, baseConfig, cache) {
	return new Promise((resolve, reject) => {
		try {
			resolve(fs.readFileSync(verifierFile));
		} catch (err) {
			if (err && err.code === 'ENOENT') {
				resolve(null);
			} else {
				reject(err);
			}
		}
	})
		.then(buffer => {
			if (!buffer) {
				return null;
			}

			const verifier = configManager.restoreVerifier(buffer);
			const fixedSourceHashes = new Map();
			fixedSourceHashes.set(baseConfig.source, baseConfig.hash);
			if (baseConfig.extends) {
				fixedSourceHashes.set(baseConfig.extends.source, baseConfig.extends.hash);
			}
			return verifier.verifyCurrentEnv({sources: fixedSourceHashes}, cache)
				.then(result => {
					if (!result.cacheKeys) {
						return null;
					}

					if (result.dependenciesChanged) {
						fs.writeFileSync(verifierFile, result.verifier.toBuffer());
					}

					return result.cacheKeys;
				});
		});
}

function resolveOptions(baseConfig, cache, optionsFile, verifierFile) {
	return configManager.fromConfig(baseConfig, {cache})
		.then(result => {
			fs.writeFileSync(optionsFile, result.generateModule());

			return result.createVerifier()
				.then(verifier => {
					fs.writeFileSync(verifierFile, verifier.toBuffer());
					return verifier.cacheKeysForCurrentEnv();
				});
		});
}

function build(projectDir, cacheDir, userOptions, powerAssert) {
	// Compute a seed based on the Node.js version and the project directory.
	// Dependency hashes may vary based on the Node.js version, e.g. with the
	// @ava/stage-4 Babel preset. Sources and dependencies paths are absolute in
	// the generated module and verifier state. Those paths wouldn't necessarily
	// be valid if the project directory changes.
	const seed = md5Hex([process.versions.node, projectDir]);

	// Ensure cacheDir exists
	makeDir.sync(cacheDir);

	// The file names predict where valid options may be cached, and thus should
	// include the seed.
	const optionsFile = path.join(cacheDir, `${seed}.babel-options.js`);
	const verifierFile = path.join(cacheDir, `${seed}.verifier.bin`);

	const baseOptions = {
		babelrc: false,
		plugins: [],
		presets: [
			['@ava/transform-test-files', {powerAssert}]
		]
	};
	if (userOptions === 'default') {
		baseOptions.presets.unshift('@ava/stage-4');
	}

	// Include object rest spread support for node versions that support it
	// natively.
	if (userOptions === 'default' && semver.satisfies(process.versions.node, '>= 8.3.0')) {
		baseOptions.plugins.push('babel-plugin-syntax-object-rest-spread');
	}

	const baseConfig = configManager.createConfig({
		dir: AVA_DIR, // Presets are resolved relative to this directory
		hash: md5Hex(JSON.stringify(baseOptions)),
		json5: false,
		options: baseOptions,
		source: SOURCE
	});

	if (userOptions !== 'default') {
		baseConfig.extend(configManager.createConfig({
			dir: projectDir,
			options: userOptions === 'inherit' ?
				{babelrc: true} :
				userOptions,
			source: path.join(projectDir, 'package.json') + '#ava.babel',
			hash: md5Hex(JSON.stringify(userOptions))
		}));
	}

	const cache = configManager.prepareCache();
	return verifyExistingOptions(verifierFile, baseConfig, cache)
		.then(cacheKeys => {
			if (cacheKeys) {
				return cacheKeys;
			}

			return resolveOptions(baseConfig, cache, optionsFile, verifierFile);
		})
		.then(cacheKeys => ({
			getOptions: require(optionsFile).getOptions,
			// Include the seed in the cache keys used to store compilation results.
			cacheKeys: Object.assign({seed}, cacheKeys)
		}));
}

module.exports = {
	validate,
	build
};