Merge pull request #6834 from loganfsmyth/caching-refactor

Expose `envName` as a top-level Babel option to avoid using environmental variables
This commit is contained in:
Logan Smyth
2017-11-24 17:42:16 -08:00
committed by GitHub
9 changed files with 657 additions and 516 deletions

View File

@@ -1,6 +1,5 @@
// @flow
import { getEnv } from "./helpers/environment";
import path from "path";
import micromatch from "micromatch";
import buildDebug from "debug";
@@ -41,20 +40,20 @@ type ConfigPart =
export default function buildConfigChain(
opts: ValidatedOptions,
envName: string,
): Array<ConfigItem> | null {
const filename = opts.filename ? path.resolve(opts.filename) : null;
const builder = new ConfigChainBuilder(
filename ? new LoadedFile(filename) : null,
);
const envKey = getEnv();
try {
builder.mergeConfigArguments(opts, process.cwd(), envKey);
builder.mergeConfigArguments(opts, process.cwd(), envName);
// resolve all .babelrc files
if (opts.babelrc !== false && filename) {
findConfigs(path.dirname(filename)).forEach(configFile =>
builder.mergeConfigFile(configFile, envKey),
findConfigs(path.dirname(filename), envName).forEach(configFile =>
builder.mergeConfigFile(configFile, envName),
);
}
} catch (e) {
@@ -85,21 +84,21 @@ class ConfigChainBuilder {
);
}
mergeConfigFile(file: ConfigFile, envKey: string) {
mergeConfigFile(file: ConfigFile, envName: string) {
if (this.seenFiles.has(file)) {
throw new Error(
`Cycle detected in Babel configuration file through "${file.filepath}".`,
);
}
const parts = flattenFileOptionsParts(file)(envKey);
const parts = flattenFileOptionsParts(file)(envName);
this.seenFiles.add(file);
parts.forEach(part => this._processConfigPart(part, envKey));
parts.forEach(part => this._processConfigPart(part, envName));
this.seenFiles.delete(file);
}
_processConfigPart(part: ConfigPart, envKey: string) {
_processConfigPart(part: ConfigPart, envName: string) {
if (part.part === "config") {
const { ignore, only } = part;
@@ -116,7 +115,10 @@ class ConfigChainBuilder {
this.configs.push(part.config);
} else {
this.mergeConfigFile(loadConfig(part.path, part.dirname), envKey);
this.mergeConfigFile(
loadConfig(part.path, part.dirname, envName),
envName,
);
}
}
}
@@ -129,7 +131,7 @@ class ConfigChainBuilder {
function flattenArgumentsOptionsParts(
opts: ValidatedOptions,
dirname: string,
envKey: string,
envName: string,
): Array<ConfigPart> {
const {
env,
@@ -142,7 +144,7 @@ function flattenArgumentsOptionsParts(
const raw = [];
if (env) {
raw.push(...flattenArgumentsEnvOptionsParts(env)(dirname)(envKey));
raw.push(...flattenArgumentsEnvOptionsParts(env)(dirname)(envName));
}
if (Object.keys(options).length > 0) {
@@ -263,7 +265,7 @@ function flattenOptionsPartsLookup(
});
}
return envKey => lookup.get(envKey) || def;
return envName => lookup.get(envName) || def;
}
/**

View File

@@ -1,29 +1,33 @@
// @flow
type CacheConfigurator = CacheConfiguratorFn & CacheConfiguratorObj;
type SimpleCacheConfigurator = SimpleCacheConfiguratorFn &
SimpleCacheConfiguratorObj;
type CacheConfiguratorFn = {
type SimpleCacheConfiguratorFn = {
(boolean): void,
<T>(handler: () => T): T,
};
type CacheConfiguratorObj = {
type SimpleCacheConfiguratorObj = {
forever: () => void,
never: () => void,
using: <T>(handler: () => T) => T,
invalidate: <T>(handler: () => T) => T,
};
type CacheEntry<ResultT> = Array<[ResultT, () => boolean]>;
type CacheEntry<ResultT, SideChannel> = Array<
[ResultT, (SideChannel) => boolean],
>;
export type { CacheConfigurator };
/**
* Given a function with a single argument, cache its results based on its argument and how it
* configures its caching behavior. Cached values are stored strongly.
*/
export function makeStrongCache<ArgT, ResultT>(
handler: (ArgT, CacheConfigurator) => ResultT,
autoPermacache?: boolean,
): ArgT => ResultT {
return makeCachedFunction(new Map(), handler, autoPermacache);
export function makeStrongCache<ArgT, ResultT, SideChannel>(
handler: (ArgT, CacheConfigurator<SideChannel>) => ResultT,
): (ArgT, SideChannel) => ResultT {
return makeCachedFunction(new Map(), handler);
}
/**
@@ -31,190 +35,182 @@ export function makeStrongCache<ArgT, ResultT>(
* configures its caching behavior. Cached values are stored weakly and the function argument must be
* an object type.
*/
export function makeWeakCache<ArgT: {} | Array<*> | $ReadOnlyArray<*>, ResultT>(
handler: (ArgT, CacheConfigurator) => ResultT,
autoPermacache?: boolean,
): ArgT => ResultT {
return makeCachedFunction(new WeakMap(), handler, autoPermacache);
export function makeWeakCache<
ArgT: {} | Array<*> | $ReadOnlyArray<*>,
ResultT,
SideChannel,
>(
handler: (ArgT, CacheConfigurator<SideChannel>) => ResultT,
): (ArgT, SideChannel) => ResultT {
return makeCachedFunction(new WeakMap(), handler);
}
type CacheMap<ArgT, ResultT> =
| Map<ArgT, CacheEntry<ResultT>>
| WeakMap<ArgT, CacheEntry<ResultT>>;
type CacheMap<ArgT, ResultT, SideChannel> =
| Map<ArgT, CacheEntry<ResultT, SideChannel>>
| WeakMap<ArgT, CacheEntry<ResultT, SideChannel>>;
function makeCachedFunction<ArgT, ResultT, Cache: CacheMap<ArgT, ResultT>>(
function makeCachedFunction<
ArgT,
ResultT,
SideChannel,
Cache: CacheMap<ArgT, ResultT, SideChannel>,
>(
callCache: Cache,
handler: (ArgT, CacheConfigurator) => ResultT,
autoPermacache: boolean = true,
): ArgT => ResultT {
return function cachedFunction(arg) {
let cachedValue: CacheEntry<ResultT> | void = callCache.get(arg);
handler: (ArgT, CacheConfigurator<SideChannel>) => ResultT,
): (ArgT, SideChannel) => ResultT {
return function cachedFunction(arg, data) {
let cachedValue: CacheEntry<ResultT, SideChannel> | void = callCache.get(
arg,
);
if (cachedValue) {
for (const [value, valid] of cachedValue) {
if (valid()) return value;
if (valid(data)) return value;
}
}
const { cache, result, deactivate } = makeCacheConfig();
const cache = new CacheConfigurator(data);
const value = handler(arg, cache);
if (autoPermacache && !result.configured) cache.forever();
if (!cache.configured()) cache.forever();
deactivate();
cache.deactivate();
if (!result.configured) {
// eslint-disable-next-line max-len
throw new Error(
[
"Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured",
"for various types of caching, using the first param of their handler functions:",
"",
"module.exports = function(api) {",
" // The API exposes the following:",
"",
" // Cache the returned value forever and don't call this function again.",
" api.cache(true);",
"",
" // Don't cache at all. Not recommended because it will be very slow.",
" api.cache(false);",
"",
" // Cached based on the value of some function. If this function returns a value different from",
" // a previously-encountered value, the plugins will re-evaluate.",
" var env = api.cache(() => process.env.NODE_ENV);",
"",
" // If testing for a specific env, we recommend specifics to avoid instantiating a plugin for",
" // any possible NODE_ENV value that might come up during plugin execution.",
' var isProd = api.cache(() => process.env.NODE_ENV === "production");',
"",
" // .cache(fn) will perform a linear search though instances to find the matching plugin based",
" // based on previous instantiated plugins. If you want to recreate the plugin and discard the",
" // previous instance whenever something changes, you may use:",
' var isProd = api.cache.invalidate(() => process.env.NODE_ENV === "production");',
"",
" // Note, we also expose the following more-verbose versions of the above examples:",
" api.cache.forever(); // api.cache(true)",
" api.cache.never(); // api.cache(false)",
" api.cache.using(fn); // api.cache(fn)",
"",
" // Return the value that will be cached.",
" return { };",
"};",
].join("\n"),
);
}
if (!result.never) {
if (result.forever) {
switch (cache.mode()) {
case "forever":
cachedValue = [[value, () => true]];
} else if (result.invalidate) {
cachedValue = [[value, result.valid]];
} else {
cachedValue = cachedValue || [];
cachedValue.push([value, result.valid]);
}
callCache.set(arg, cachedValue);
callCache.set(arg, cachedValue);
break;
case "invalidate":
cachedValue = [[value, cache.validator()]];
callCache.set(arg, cachedValue);
break;
case "valid":
if (cachedValue) {
cachedValue.push([value, cache.validator()]);
} else {
cachedValue = [[value, cache.validator()]];
callCache.set(arg, cachedValue);
}
}
return value;
};
}
function makeCacheConfig(): {
cache: CacheConfigurator,
result: *,
deactivate: () => void,
} {
const pairs = [];
class CacheConfigurator<SideChannel = void> {
_active: boolean = true;
_never: boolean = false;
_forever: boolean = false;
_invalidate: boolean = false;
const result = {
configured: false,
never: false,
forever: false,
invalidate: false,
valid: () => pairs.every(([key, fn]) => key === fn()),
};
_configured: boolean = false;
let active = true;
const deactivate = () => {
active = false;
};
_pairs: Array<[mixed, (SideChannel) => mixed]> = [];
const cache: CacheConfigurator = Object.assign(
(function cacheFn(val) {
if (typeof val === "boolean") {
if (val) cache.forever();
else cache.never();
return;
}
_data: SideChannel;
return cache.using(val);
}: any),
({
forever() {
if (!active) {
throw new Error(
"Cannot change caching after evaluation has completed.",
);
}
if (result.never) {
throw new Error("Caching has already been configured with .never()");
}
result.forever = true;
result.configured = true;
},
never() {
if (!active) {
throw new Error(
"Cannot change caching after evaluation has completed.",
);
}
if (result.forever) {
throw new Error(
"Caching has already been configured with .forever()",
);
}
result.never = true;
result.configured = true;
},
using<T>(handler: () => T): T {
if (!active) {
throw new Error(
"Cannot change caching after evaluation has completed.",
);
}
if (result.never || result.forever) {
throw new Error(
"Caching has already been configured with .never or .forever()",
);
}
result.configured = true;
constructor(data: SideChannel) {
this._data = data;
}
const key = handler();
pairs.push([key, handler]);
return key;
},
invalidate<T>(handler: () => T): T {
if (!active) {
throw new Error(
"Cannot change caching after evaluation has completed.",
);
}
if (result.never || result.forever) {
throw new Error(
"Caching has already been configured with .never or .forever()",
);
}
result.invalidate = true;
result.configured = true;
simple() {
return makeSimpleConfigurator(this);
}
const key = handler();
pairs.push([key, handler]);
return key;
},
}: CacheConfiguratorObj),
);
mode() {
if (this._never) return "never";
if (this._forever) return "forever";
if (this._invalidate) return "invalidate";
return "valid";
}
return { cache, result, deactivate };
forever() {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._never) {
throw new Error("Caching has already been configured with .never()");
}
this._forever = true;
this._configured = true;
}
never() {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._forever) {
throw new Error("Caching has already been configured with .forever()");
}
this._never = true;
this._configured = true;
}
using<T>(handler: SideChannel => T): T {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._never || this._forever) {
throw new Error(
"Caching has already been configured with .never or .forever()",
);
}
this._configured = true;
const key = handler(this._data);
this._pairs.push([key, handler]);
return key;
}
invalidate<T>(handler: SideChannel => T): T {
if (!this._active) {
throw new Error("Cannot change caching after evaluation has completed.");
}
if (this._never || this._forever) {
throw new Error(
"Caching has already been configured with .never or .forever()",
);
}
this._invalidate = true;
this._configured = true;
const key = handler(this._data);
this._pairs.push([key, handler]);
return key;
}
validator(): SideChannel => boolean {
const pairs = this._pairs;
return (data: SideChannel) => pairs.every(([key, fn]) => key === fn(data));
}
deactivate() {
this._active = false;
}
configured() {
return this._configured;
}
}
function makeSimpleConfigurator(
cache: CacheConfigurator<any>,
): SimpleCacheConfigurator {
function cacheFn(val) {
if (typeof val === "boolean") {
if (val) cache.forever();
else cache.never();
return;
}
return cache.using(val);
}
cacheFn.forever = () => cache.forever();
cacheFn.never = () => cache.never();
cacheFn.using = cb => cache.using(() => cb());
cacheFn.invalidate = cb => cache.invalidate(() => cb());
return (cacheFn: any);
}

View File

@@ -5,8 +5,7 @@ import path from "path";
import fs from "fs";
import json5 from "json5";
import resolve from "resolve";
import { getEnv } from "../../helpers/environment";
import { makeStrongCache } from "../../caching";
import { makeStrongCache, type CacheConfigurator } from "../../caching";
const debug = buildDebug("babel:config:loading:files:configuration");
@@ -21,7 +20,10 @@ const BABELRC_JS_FILENAME = ".babelrc.js";
const PACKAGE_FILENAME = "package.json";
const BABELIGNORE_FILENAME = ".babelignore";
export function findConfigs(dirname: string): Array<ConfigFile> {
export function findConfigs(
dirname: string,
envName: string,
): Array<ConfigFile> {
let foundConfig = false;
let foundIgnore = false;
@@ -47,7 +49,7 @@ export function findConfigs(dirname: string): Array<ConfigFile> {
PACKAGE_FILENAME,
].reduce((previousConfig: ConfigFile | null, name) => {
const filepath = path.join(loc, name);
const config = readConfig(filepath);
const config = readConfig(filepath, envName);
if (config && previousConfig) {
throw new Error(
@@ -77,10 +79,14 @@ export function findConfigs(dirname: string): Array<ConfigFile> {
return confs;
}
export function loadConfig(name: string, dirname: string): ConfigFile {
export function loadConfig(
name: string,
dirname: string,
envName: string,
): ConfigFile {
const filepath = resolve.sync(name, { basedir: dirname });
const conf = readConfig(filepath);
const conf = readConfig(filepath, envName);
if (!conf) {
throw new Error(`Config file ${filepath} contains no configuration data`);
}
@@ -93,83 +99,86 @@ export function loadConfig(name: string, dirname: string): ConfigFile {
* Read the given config file, returning the result. Returns null if no config was found, but will
* throw if there are parsing errors while loading a config.
*/
function readConfig(filepath) {
function readConfig(filepath, envName) {
return path.extname(filepath) === ".js"
? readConfigJS(filepath)
? readConfigJS(filepath, { envName })
: readConfigFile(filepath);
}
const LOADING_CONFIGS = new Set();
const readConfigJS = makeStrongCache((filepath, cache) => {
if (!fs.existsSync(filepath)) {
cache.forever();
return null;
}
// The `require()` call below can make this code reentrant if a require hook like @babel/register has been
// loaded into the system. That would cause Babel to attempt to compile the `.babelrc.js` file as it loads
// below. To cover this case, we auto-ignore re-entrant config processing.
if (LOADING_CONFIGS.has(filepath)) {
cache.never();
const readConfigJS = makeStrongCache(
(filepath, cache: CacheConfigurator<{ envName: string }>) => {
if (!fs.existsSync(filepath)) {
cache.forever();
return null;
}
// The `require()` call below can make this code reentrant if a require hook like @babel/register has been
// loaded into the system. That would cause Babel to attempt to compile the `.babelrc.js` file as it loads
// below. To cover this case, we auto-ignore re-entrant config processing.
if (LOADING_CONFIGS.has(filepath)) {
cache.never();
debug("Auto-ignoring usage of config %o.", filepath);
return {
filepath,
dirname: path.dirname(filepath),
options: {},
};
}
let options;
try {
LOADING_CONFIGS.add(filepath);
// $FlowIssue
const configModule = (require(filepath): mixed);
options =
configModule && configModule.__esModule
? configModule.default || undefined
: configModule;
} catch (err) {
err.message = `${filepath}: Error while loading config - ${err.message}`;
throw err;
} finally {
LOADING_CONFIGS.delete(filepath);
}
if (typeof options === "function") {
options = options({
cache: cache.simple(),
// Expose ".env()" so people can easily get the same env that we expose using the "env" key.
env: () => cache.using(data => data.envName),
async: () => false,
});
if (!cache.configured()) throwConfigError();
}
if (!options || typeof options !== "object" || Array.isArray(options)) {
throw new Error(
`${filepath}: Configuration should be an exported JavaScript object.`,
);
}
if (typeof options.then === "function") {
throw new Error(
`You appear to be using an async configuration, ` +
`which your current version of Babel does not support. ` +
`We may add support for this in the future, ` +
`but if you're on the most recent version of @babel/core and still ` +
`seeing this error, then you'll need to synchronously return your config.`,
);
}
debug("Auto-ignoring usage of config %o.", filepath);
return {
filepath,
dirname: path.dirname(filepath),
options: {},
options,
};
}
let options;
try {
LOADING_CONFIGS.add(filepath);
// $FlowIssue
const configModule = (require(filepath): mixed);
options =
configModule && configModule.__esModule
? configModule.default || undefined
: configModule;
} catch (err) {
err.message = `${filepath}: Error while loading config - ${err.message}`;
throw err;
} finally {
LOADING_CONFIGS.delete(filepath);
}
if (typeof options === "function") {
options = options({
cache,
// Expose ".env()" so people can easily get the same env that we expose using the "env" key.
env: () => cache.using(() => getEnv()),
async: () => false,
});
} else {
cache.forever();
}
if (!options || typeof options !== "object" || Array.isArray(options)) {
throw new Error(
`${filepath}: Configuration should be an exported JavaScript object.`,
);
}
if (typeof options.then === "function") {
throw new Error(
`You appear to be using an async configuration, ` +
`which your current version of Babel does not support. ` +
`We may add support for this in the future, ` +
`but if you're on the most recent version of @babel/core and still ` +
`seeing this error, then you'll need to synchronously return your config.`,
);
}
return {
filepath,
dirname: path.dirname(filepath),
options,
};
}, false /* autoPermacache */);
},
);
const readConfigFile = makeStaticFileCache((filepath, content) => {
let options;
@@ -239,3 +248,40 @@ function fileMtime(filepath: string): number | null {
return null;
}
function throwConfigError() {
throw new Error(`\
Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured
for various types of caching, using the first param of their handler functions:
module.exports = function(api) {
// The API exposes the following:
// Cache the returned value forever and don't call this function again.
api.cache(true);
// Don't cache at all. Not recommended because it will be very slow.
api.cache(false);
// Cached based on the value of some function. If this function returns a value different from
// a previously-encountered value, the plugins will re-evaluate.
var env = api.cache(() => process.env.NODE_ENV);
// If testing for a specific env, we recommend specifics to avoid instantiating a plugin for
// any possible NODE_ENV value that might come up during plugin execution.
var isProd = api.cache(() => process.env.NODE_ENV === "production");
// .cache(fn) will perform a linear search though instances to find the matching plugin based
// based on previous instantiated plugins. If you want to recreate the plugin and discard the
// previous instance whenever something changes, you may use:
var isProd = api.cache.invalidate(() => process.env.NODE_ENV === "production");
// Note, we also expose the following more-verbose versions of the above examples:
api.cache.forever(); // api.cache(true)
api.cache.never(); // api.cache(false)
api.cache.using(fn); // api.cache(fn)
// Return the value that will be cached.
return { };
};`);
}

View File

@@ -6,7 +6,7 @@ import merge from "lodash/merge";
import buildConfigChain, { type ConfigItem } from "./build-config-chain";
import traverse from "@babel/traverse";
import clone from "lodash/clone";
import { makeWeakCache } from "./caching";
import { makeWeakCache, type CacheConfigurator } from "./caching";
import { getEnv } from "./helpers/environment";
import { validate, type ValidatedOptions, type PluginItem } from "./options";
@@ -46,14 +46,14 @@ class OptionManager {
* - `dirname` is used to resolve plugins relative to it.
*/
mergeOptions(config: MergeOptions, pass?: Array<Plugin>) {
mergeOptions(config: MergeOptions, pass?: Array<Plugin>, envName: string) {
const result = loadConfig(config);
const plugins = result.plugins.map(descriptor =>
loadPluginDescriptor(descriptor),
loadPluginDescriptor(descriptor, envName),
);
const presets = result.presets.map(descriptor =>
loadPresetDescriptor(descriptor),
loadPresetDescriptor(descriptor, envName),
);
const passPerPreset = config.options.passPerPreset;
@@ -70,7 +70,11 @@ class OptionManager {
}
presets.forEach((presetConfig, i) => {
this.mergeOptions(presetConfig, presetPasses ? presetPasses[i] : pass);
this.mergeOptions(
presetConfig,
presetPasses ? presetPasses[i] : pass,
envName,
);
});
}
@@ -99,12 +103,14 @@ class OptionManager {
init(inputOpts: {}) {
const args = validate("arguments", inputOpts);
const configChain = buildConfigChain(args);
const { envName = getEnv() } = args;
const configChain = buildConfigChain(args, envName);
if (!configChain) return null;
try {
for (const config of configChain) {
this.mergeOptions(config);
this.mergeOptions(config, undefined, envName);
}
} catch (e) {
// There are a few case where thrown errors will try to annotate themselves multiple times, so
@@ -127,6 +133,7 @@ class OptionManager {
.filter(plugins => plugins.length > 0)
.map(plugins => ({ plugins }));
opts.passPerPreset = opts.presets.length > 0;
opts.envName = envName;
return {
options: opts,
@@ -182,13 +189,13 @@ const loadConfig = makeWeakCache((config: MergeOptions): {
const loadDescriptor = makeWeakCache(
(
{ value, options = {}, dirname, alias }: BasicDescriptor,
cache,
cache: CacheConfigurator<{ envName: string }>,
): LoadedDescriptor => {
let item = value;
if (typeof value === "function") {
const api = Object.assign(Object.create(context), {
cache,
env: () => cache.using(() => getEnv()),
cache: cache.simple(),
env: () => cache.using(data => data.envName),
async: () => false,
});
@@ -222,7 +229,10 @@ const loadDescriptor = makeWeakCache(
/**
* Instantiate a plugin for the given descriptor, returning the plugin/options pair.
*/
function loadPluginDescriptor(descriptor: BasicDescriptor): Plugin {
function loadPluginDescriptor(
descriptor: BasicDescriptor,
envName: string,
): Plugin {
if (descriptor.value instanceof Plugin) {
if (descriptor.options) {
throw new Error(
@@ -233,11 +243,16 @@ function loadPluginDescriptor(descriptor: BasicDescriptor): Plugin {
return descriptor.value;
}
return instantiatePlugin(loadDescriptor(descriptor));
return instantiatePlugin(loadDescriptor(descriptor, { envName }), {
envName,
});
}
const instantiatePlugin = makeWeakCache(
({ value, options, dirname, alias }: LoadedDescriptor, cache): Plugin => {
(
{ value, options, dirname, alias }: LoadedDescriptor,
cache: CacheConfigurator<{ envName: string }>,
): Plugin => {
const pluginObj = validatePluginObject(value);
const plugin = Object.assign({}, pluginObj);
@@ -254,8 +269,8 @@ const instantiatePlugin = makeWeakCache(
};
// If the inherited plugin changes, reinstantiate this plugin.
const inherits = cache.invalidate(() =>
loadPluginDescriptor(inheritsDescriptor),
const inherits = cache.invalidate(data =>
loadPluginDescriptor(inheritsDescriptor, data.envName),
);
plugin.pre = chain(inherits.pre, plugin.pre);
@@ -277,8 +292,11 @@ const instantiatePlugin = makeWeakCache(
/**
* Generate a config object that will act as the root of a new nested config.
*/
const loadPresetDescriptor = (descriptor: BasicDescriptor): MergeOptions => {
return instantiatePreset(loadDescriptor(descriptor));
const loadPresetDescriptor = (
descriptor: BasicDescriptor,
envName: string,
): MergeOptions => {
return instantiatePreset(loadDescriptor(descriptor, { envName }));
};
const instantiatePreset = makeWeakCache(

View File

@@ -28,6 +28,10 @@ const ROOT_VALIDATORS: ValidatorSet = {
>),
code: (assertBoolean: Validator<$PropertyType<ValidatedOptions, "code">>),
ast: (assertBoolean: Validator<$PropertyType<ValidatedOptions, "ast">>),
envName: (assertString: Validator<
$PropertyType<ValidatedOptions, "envName">,
>),
};
const NONPRESET_VALIDATORS: ValidatorSet = {
@@ -130,6 +134,7 @@ export type ValidatedOptions = {
code?: boolean,
ast?: boolean,
inputSourceMap?: RootInputSourceMapOption,
envName?: string,
extends?: string,
env?: EnvSet<ValidatedOptions>,