add showConfig support (#11588)

* draft: showConfig support

* feat: pass through showConfig command options

* update test file

* refactor: add createLogger to makeChainWalker

* serializing dynamic plugin instance

* fix flow errors

* chore: add tests on extended config

* fix: do not print empty presets

* add more test cases

* add windows testcases

* address review comments

* throw error when showConfigPath does not exist

* print reason when showConfig is targetting an ignored file

* remove showConfig: boolean

* refactor: simplify environment flag name

* rename test fixtures

* fix: throw when SHOW_CONFIG_FOR is not a regular file

* cleanup test fixtures

* add test on only

* Update packages/babel-core/src/config/files/configuration.js

Co-authored-by: Brian Ng <bng412@gmail.com>

* address review comments

* update test fixtures

* feat: sort config items in ascending priority

Co-authored-by: Brian Ng <bng412@gmail.com>
This commit is contained in:
Huáng Jùnliàng 2020-07-30 09:24:19 -04:00 committed by GitHub
parent 374a253d0c
commit 164a93945d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 827 additions and 39 deletions

View File

@ -0,0 +1,14 @@
{
"sourceType": "script",
"overrides": [
{
"test": "src/index.js",
"sourceType": "module"
}
],
"env": {
"test": {
"ignore": ["./src/index.js"]
}
}
}

View File

@ -0,0 +1,7 @@
{
"args": ["./src/index.js"],
"env": {
"BABEL_SHOW_CONFIG_FOR": "./src/index.js"
},
"os": ["darwin", "linux"]
}

View File

@ -0,0 +1 @@
No config is applied to "<CWD>/src/index.js" because it matches one of `ignore: ["./src/index.js"]` from "<CWD>"

View File

@ -0,0 +1,13 @@
{
"sourceType": "script",
"overrides": [
{
"test": "src/foo.js",
"sourceType": "module"
},
{
"test": "src/bar.js",
"sourceType": "script"
}
]
}

View File

@ -0,0 +1,7 @@
{
"args": ["./src", "--out-file", "./test.js"],
"os": ["win32"],
"env": {
"BABEL_SHOW_CONFIG_FOR": "./src/foo.js"
}
}

View File

@ -0,0 +1,31 @@
Babel configs on "<CWD>/src/foo.js" (ascending priority):
config <CWD>/babel.config.json
{
"sourceType": "script"
}
config <CWD>/babel.config.json .overrides[0]
{
"test": "src/foo.js",
"sourceType": "module"
}
config <CWD>/.babelrc
{}
programmatic options from @babel/cli
{
"sourceFileName": "src/foo.js",
"presets": [
"<ROOTDIR>//packages//babel-preset-react"
],
"plugins": [
"<ROOTDIR>//packages//babel-plugin-transform-arrow-functions",
"<ROOTDIR>//packages//babel-plugin-transform-strict-mode",
"<ROOTDIR>//packages//babel-plugin-transform-modules-commonjs"
],
"caller": {
"name": "@babel/cli"
},
"filename": "src//foo.js"
}

View File

@ -0,0 +1,13 @@
{
"sourceType": "script",
"overrides": [
{
"test": "src/foo.js",
"sourceType": "module"
},
{
"test": "src/bar.js",
"sourceType": "script"
}
]
}

View File

@ -0,0 +1,7 @@
{
"args": ["./src", "--out-file", "./test.js"],
"os": ["darwin", "linux"],
"env": {
"BABEL_SHOW_CONFIG_FOR": "./src/foo.js"
}
}

View File

@ -0,0 +1,31 @@
Babel configs on "<CWD>/src/foo.js" (ascending priority):
config <CWD>/babel.config.json
{
"sourceType": "script"
}
config <CWD>/babel.config.json .overrides[0]
{
"test": "src/foo.js",
"sourceType": "module"
}
config <CWD>/.babelrc
{}
programmatic options from @babel/cli
{
"sourceFileName": "src/foo.js",
"presets": [
"<ROOTDIR>/packages/babel-preset-react"
],
"plugins": [
"<ROOTDIR>/packages/babel-plugin-transform-arrow-functions",
"<ROOTDIR>/packages/babel-plugin-transform-strict-mode",
"<ROOTDIR>/packages/babel-plugin-transform-modules-commonjs"
],
"caller": {
"name": "@babel/cli"
},
"filename": "src/foo.js"
}

View File

@ -0,0 +1,14 @@
{
"sourceType": "script",
"overrides": [
{
"test": "src/index.js",
"sourceType": "module"
}
],
"env": {
"test": {
"only": ["./src/unicorn.js"]
}
}
}

View File

@ -0,0 +1,7 @@
{
"args": ["./src/index.js"],
"env": {
"BABEL_SHOW_CONFIG_FOR": "./src/index.js"
},
"os": ["darwin", "linux"]
}

View File

@ -0,0 +1 @@
No config is applied to "<CWD>/src/index.js" because it fails to match one of `only: ["./src/unicorn.js"]` from "<CWD>"

View File

@ -0,0 +1,13 @@
{
"sourceType": "script",
"overrides": [
{
"test": "src/foo.js",
"sourceType": "module"
},
{
"test": "src/bar.js",
"sourceType": "script"
}
]
}

View File

@ -0,0 +1,7 @@
{
"args": ["./src", "-d", "lib"],
"env": {
"BABEL_SHOW_CONFIG_FOR": "./src/foo.js"
},
"os": ["win32"]
}

View File

@ -0,0 +1 @@
"use strict";

View File

@ -0,0 +1,32 @@
Babel configs on "<CWD>/src/foo.js" (ascending priority):
config <CWD>/babel.config.json
{
"sourceType": "script"
}
config <CWD>/babel.config.json .overrides[0]
{
"test": "src/foo.js",
"sourceType": "module"
}
config <CWD>/.babelrc
{}
programmatic options from @babel/cli
{
"sourceFileName": "../src/foo.js",
"presets": [
"<ROOTDIR>//packages//babel-preset-react"
],
"plugins": [
"<ROOTDIR>//packages//babel-plugin-transform-arrow-functions",
"<ROOTDIR>//packages//babel-plugin-transform-strict-mode",
"<ROOTDIR>//packages//babel-plugin-transform-modules-commonjs"
],
"caller": {
"name": "@babel/cli"
},
"filename": "src//foo.js"
}
Successfully compiled 1 file with Babel (123ms).

View File

@ -0,0 +1,40 @@
module.exports = {
sourceType: "script",
plugins: [require("@foo/babel-plugin-1")],
extends: "./my-extended.js",
overrides: [
{
test: "src/index.js",
plugins: [["@foo/babel-plugin-2", { noDocumentAll: true }]],
env: {
test: {
plugins: [
"@foo/babel-plugin-1",
[
{ name: "@foo/inline-babel-plugin-1", visitor: { Program() {} } },
{ noDocumentAll: true },
],
],
},
},
},
{
exclude: "src/index.js",
plugins: ["@foo/babel-plugin-4"],
},
],
env: {
test: {
plugins: [
[
"@foo/babel-plugin-3",
{ noDocumentAll: true },
"@foo/babel-plugin-three",
],
],
},
development: {
plugins: ["@foo/babel-plugin-4"],
},
},
};

View File

@ -0,0 +1,47 @@
module.exports = {
sourceMaps: false,
presets: ["@foo/babel-preset-1"],
overrides: [
{
test: "src/index.js",
presets: [["@foo/babel-preset-2", { noDocumentAll: true }]],
env: {
test: {
presets: [
"@foo/babel-preset-1",
[
{
name: "@foo/inline-babel-preset-1",
plugins: [
{
name: "@foo/inline-babel-plugin-1",
visitor: { Program() {} },
},
],
},
{ noDocumentAll: true },
],
],
},
},
},
{
exclude: "src/index.js",
presets: ["@foo/babel-preset-4"],
},
],
env: {
test: {
presets: [
[
"@foo/babel-preset-3",
{ noDocumentAll: true },
"@foo/babel-preset-three",
],
],
},
development: {
presets: ["@foo/babel-preset-4"],
},
},
};

View File

@ -0,0 +1,6 @@
module.exports = (api) => ({
name: "@foo/" + __dirname,
visitor: {
Program() {}
}
})

View File

@ -0,0 +1,6 @@
module.exports = (api) => ({
name: "@foo/" + __dirname,
visitor: {
Program() {}
}
})

View File

@ -0,0 +1,6 @@
module.exports = (api) => ({
name: "@foo/" + __dirname,
visitor: {
Program() {}
}
})

View File

@ -0,0 +1,6 @@
module.exports = (api) => ({
name: "@foo/" + __dirname,
visitor: {
Program() {}
}
})

View File

@ -0,0 +1,4 @@
module.exports = (api) => ({
name: "@foo/" + __dirname,
plugins: ["@foo/" + __dirname.replace("preset", "plugin")]
})

View File

@ -0,0 +1,4 @@
module.exports = (api) => ({
name: "@foo/" + __dirname,
plugins: ["@foo/" + __dirname.replace("preset", "plugin")]
})

View File

@ -0,0 +1,4 @@
module.exports = (api) => ({
name: "@foo/" + __dirname,
plugins: ["@foo/" + __dirname.replace("preset", "plugin")]
})

View File

@ -0,0 +1,4 @@
module.exports = (api) => ({
name: "@foo/" + __dirname,
plugins: ["@foo/" + __dirname.replace("preset", "plugin")]
})

View File

@ -0,0 +1,8 @@
{
"args": ["--config-file", "./my-config.js", "./src/index.js"],
"env": {
"BABEL_ENV": "test",
"BABEL_SHOW_CONFIG_FOR": "./src/index.js"
},
"os": ["darwin", "linux"]
}

View File

@ -0,0 +1,127 @@
Babel configs on "<CWD>/src/index.js" (ascending priority):
config <CWD>/my-extended.js
{
"sourceMaps": false,
"presets": [
"@foo/babel-preset-1"
]
}
config <CWD>/my-extended.js .env["test"]
{
"presets": [
[
"@foo/babel-preset-3",
{
"noDocumentAll": true
},
"@foo/babel-preset-three"
]
]
}
config <CWD>/my-extended.js .overrides[0]
{
"test": "src/index.js",
"presets": [
[
"@foo/babel-preset-2",
{
"noDocumentAll": true
}
]
]
}
config <CWD>/my-extended.js .overrides[0].env["test"]
{
"presets": [
"@foo/babel-preset-1",
[
{
"name": "@foo/inline-babel-preset-1",
"plugins": [
{
"name": "@foo/inline-babel-plugin-1",
"visitor": {}
}
]
},
{
"noDocumentAll": true
}
]
]
}
config <CWD>/my-config.js
{
"sourceType": "script",
"plugins": [
"[Function: (api) => ({/n name: /"@foo//" + __dirname,/n visitor ... ]"
],
"extends": "./my-extended.js"
}
config <CWD>/my-config.js .env["test"]
{
"plugins": [
[
"@foo/babel-plugin-3",
{
"noDocumentAll": true
},
"@foo/babel-plugin-three"
]
]
}
config <CWD>/my-config.js .overrides[0]
{
"test": "src/index.js",
"plugins": [
[
"@foo/babel-plugin-2",
{
"noDocumentAll": true
}
]
]
}
config <CWD>/my-config.js .overrides[0].env["test"]
{
"plugins": [
"@foo/babel-plugin-1",
[
{
"name": "@foo/inline-babel-plugin-1",
"visitor": {}
},
{
"noDocumentAll": true
}
]
]
}
config <CWD>/.babelrc
{}
programmatic options from @babel/cli
{
"sourceFileName": "./src/index.js",
"presets": [
"<ROOTDIR>/packages/babel-preset-react"
],
"plugins": [
"<ROOTDIR>/packages/babel-plugin-transform-arrow-functions",
"<ROOTDIR>/packages/babel-plugin-transform-strict-mode",
"<ROOTDIR>/packages/babel-plugin-transform-modules-commonjs"
],
"configFile": "./my-config.js",
"caller": {
"name": "@babel/cli"
},
"filename": "./src/index.js"
}

View File

@ -0,0 +1,13 @@
{
"sourceType": "script",
"overrides": [
{
"test": "src/foo.js",
"sourceType": "module"
},
{
"test": "src/bar.js",
"sourceType": "script"
}
]
}

View File

@ -0,0 +1,8 @@
{
"args": ["./src", "-d", "lib"],
"env": {
"BABEL_SHOW_CONFIG_FOR": "./src"
},
"os": ["win32"],
"stderrContains": true
}

View File

@ -0,0 +1 @@
Error: <CWD>\src: BABEL_SHOW_CONFIG_FOR must refer to a regular file, directories are not supported.

View File

@ -0,0 +1,13 @@
{
"sourceType": "script",
"overrides": [
{
"test": "src/foo.js",
"sourceType": "module"
},
{
"test": "src/bar.js",
"sourceType": "script"
}
]
}

View File

@ -0,0 +1,8 @@
{
"args": ["./src", "-d", "lib"],
"env": {
"BABEL_SHOW_CONFIG_FOR": "./src"
},
"os": ["darwin", "linux"],
"stderrContains": true
}

View File

@ -0,0 +1 @@
Error: <CWD>/src: BABEL_SHOW_CONFIG_FOR must refer to a regular file, directories are not supported.

View File

@ -3,12 +3,14 @@ const helper = require("@babel/helper-fixtures");
const rimraf = require("rimraf");
const { sync: makeDirSync } = require("make-dir");
const child = require("child_process");
const escapeRegExp = require("lodash/escapeRegExp");
const merge = require("lodash/merge");
const path = require("path");
const fs = require("fs");
const fixtureLoc = path.join(__dirname, "fixtures");
const tmpLoc = path.join(__dirname, "tmp");
const rootDir = path.resolve(__dirname, "../../..");
const fileFilter = function (x) {
return x !== ".DS_Store";
@ -19,12 +21,12 @@ const outputFileSync = function (filePath, data) {
fs.writeFileSync(filePath, data);
};
const presetLocs = [path.join(__dirname, "../../babel-preset-react")];
const presetLocs = [path.join(rootDir, "./packages/babel-preset-react")];
const pluginLocs = [
path.join(__dirname, "/../../babel-plugin-transform-arrow-functions"),
path.join(__dirname, "/../../babel-plugin-transform-strict-mode"),
path.join(__dirname, "/../../babel-plugin-transform-modules-commonjs"),
path.join(rootDir, "./packages/babel-plugin-transform-arrow-functions"),
path.join(rootDir, "./packages/babel-plugin-transform-strict-mode"),
path.join(rootDir, "./packages/babel-plugin-transform-modules-commonjs"),
].join(",");
const readDir = function (loc, filter) {
@ -50,13 +52,21 @@ const saveInFiles = function (files) {
};
const normalizeOutput = function (str, cwd) {
let prev;
do {
prev = str;
str = str.replace(cwd, "<CWD>");
} while (str !== prev);
return str.replace(/\(\d+ms\)/g, "(123ms)");
let result = str
.replace(/\(\d+ms\)/g, "(123ms)")
.replace(new RegExp(escapeRegExp(cwd), "g"), "<CWD>")
// (non-win32) /foo/babel/packages -> <CWD>/packages
// (win32) C:\foo\babel\packages -> <CWD>\packages
.replace(new RegExp(escapeRegExp(rootDir), "g"), "<ROOTDIR>");
if (process.platform === "win32") {
result = result
// C:\\foo\\babel\\packages -> <CWD>\\packages (in js string literal)
.replace(
new RegExp(escapeRegExp(rootDir.replace(/\\/g, "\\\\")), "g"),
"<ROOTDIR>",
);
}
return result;
};
const assertTest = function (stdout, stderr, opts, cwd) {
@ -132,8 +142,9 @@ const buildTest = function (binName, testName, opts) {
}
args = args.concat(opts.args);
const env = { ...process.env, ...opts.env };
const spawn = child.spawn(process.execPath, args);
const spawn = child.spawn(process.execPath, args, { env });
let stderr = "";
let stdout = "";

View File

@ -12,6 +12,7 @@ import {
type CallerMetadata,
} from "./validation/options";
import pathPatternToRegex from "./pattern-to-regex";
import { ConfigPrinter, ChainFormatter } from "./printer";
const debug = buildDebug("babel:config:config-chain");
@ -53,6 +54,7 @@ export type ConfigContext = {
root: string,
envName: string,
caller: CallerMetadata | void,
showConfig: boolean,
};
/**
@ -81,6 +83,7 @@ export const buildPresetChainWalker: (
overrides: (preset, index) => loadPresetOverridesDescriptors(preset)(index),
overridesEnv: (preset, index, envName) =>
loadPresetOverridesEnvDescriptors(preset)(index)(envName),
createLogger: () => () => {}, // Currently we don't support logging how preset is expanded
});
const loadPresetDescriptors = makeWeakCacheSync((preset: PresetInstance) =>
buildRootDescriptors(preset, preset.alias, createUncachedDescriptors),
@ -134,14 +137,19 @@ export function* buildRootChain(
opts: ValidatedOptions,
context: ConfigContext,
): Handler<RootConfigChain | null> {
let configReport, babelRcReport;
const programmaticLogger = new ConfigPrinter();
const programmaticChain = yield* loadProgrammaticChain(
{
options: opts,
dirname: context.cwd,
},
context,
undefined,
programmaticLogger,
);
if (!programmaticChain) return null;
const programmaticReport = programmaticLogger.output();
let configFile;
if (typeof opts.configFile === "string") {
@ -163,10 +171,17 @@ export function* buildRootChain(
let babelrcRootsDirectory = context.cwd;
const configFileChain = emptyChain();
const configFileLogger = new ConfigPrinter();
if (configFile) {
const validatedFile = validateConfigFile(configFile);
const result = yield* loadFileChain(validatedFile, context);
const result = yield* loadFileChain(
validatedFile,
context,
undefined,
configFileLogger,
);
if (!result) return null;
configReport = configFileLogger.output();
// Allow config files to toggle `.babelrc` resolution on and off and
// specify where the roots are.
@ -208,16 +223,32 @@ export function* buildRootChain(
}
if (babelrcFile) {
const validatedFile = validateBabelrcFile(babelrcFile);
const babelrcLogger = new ConfigPrinter();
const result = yield* loadFileChain(
validateBabelrcFile(babelrcFile),
validatedFile,
context,
undefined,
babelrcLogger,
);
if (!result) return null;
babelRcReport = babelrcLogger.output();
mergeChain(fileChain, result);
}
}
if (context.showConfig) {
console.log(
// $FlowIgnore: context.showConfig implies context.filename is not null
`Babel configs on "${context.filename}" (ascending priority):\n` +
// print config by the order of ascending priority
[configReport, babelRcReport, programmaticReport]
.filter(x => !!x)
.join("\n\n"),
);
return null;
}
// Insert file chain in front so programmatic options have priority
// over configuration file chain items.
const chain = mergeChain(
@ -317,6 +348,8 @@ const loadProgrammaticChain = makeChainWalker({
index,
envName,
),
createLogger: (input, context, baseLogger) =>
buildProgrammaticLogger(input, context, baseLogger),
});
/**
@ -328,6 +361,8 @@ const loadFileChain = makeChainWalker({
overrides: (file, index) => loadFileOverridesDescriptors(file)(index),
overridesEnv: (file, index, envName) =>
loadFileOverridesEnvDescriptors(file)(index)(envName),
createLogger: (file, context, baseLogger) =>
buildFileLogger(file.filepath, context, baseLogger),
});
const loadFileDescriptors = makeWeakCacheSync((file: ValidatedFile) =>
buildRootDescriptors(file, file.filepath, createUncachedDescriptors),
@ -367,10 +402,32 @@ const loadFileOverridesEnvDescriptors = makeWeakCacheSync(
),
);
function buildFileLogger(
filepath: string,
context: ConfigContext,
baseLogger: ConfigPrinter | void,
) {
if (!baseLogger) {
return () => {};
}
return baseLogger.configure(context.showConfig, ChainFormatter.Config, {
filepath,
});
}
function buildRootDescriptors({ dirname, options }, alias, descriptors) {
return descriptors(dirname, options, alias);
}
function buildProgrammaticLogger(_, context, baseLogger: ConfigPrinter | void) {
if (!baseLogger) {
return () => {};
}
return baseLogger.configure(context.showConfig, ChainFormatter.Programmatic, {
callerName: context.caller?.name,
});
}
function buildEnvDescriptors(
{ dirname, options },
alias,
@ -418,41 +475,68 @@ function makeChainWalker<ArgT: { options: ValidatedOptions, dirname: string }>({
env,
overrides,
overridesEnv,
createLogger,
}: {|
root: ArgT => OptionsAndDescriptors,
env: (ArgT, string) => OptionsAndDescriptors | null,
overrides: (ArgT, number) => OptionsAndDescriptors,
overridesEnv: (ArgT, number, string) => OptionsAndDescriptors | null,
createLogger: (
ArgT,
ConfigContext,
ConfigPrinter | void,
) => (OptionsAndDescriptors, ?number, ?string) => void,
|}): (
ArgT,
ConfigContext,
Set<ConfigFile> | void,
files?: Set<ConfigFile> | void,
baseLogger: ConfigPrinter | void,
) => Handler<ConfigChain | null> {
return function* (input, context, files = new Set()) {
return function* (input, context, files = new Set(), baseLogger) {
const { dirname } = input;
const flattenedConfigs = [];
const flattenedConfigs: Array<{|
config: OptionsAndDescriptors,
index: ?number,
envName: ?string,
|}> = [];
const rootOpts = root(input);
if (configIsApplicable(rootOpts, dirname, context)) {
flattenedConfigs.push(rootOpts);
flattenedConfigs.push({
config: rootOpts,
envName: undefined,
index: undefined,
});
const envOpts = env(input, context.envName);
if (envOpts && configIsApplicable(envOpts, dirname, context)) {
flattenedConfigs.push(envOpts);
flattenedConfigs.push({
config: envOpts,
envName: context.envName,
index: undefined,
});
}
(rootOpts.options.overrides || []).forEach((_, index) => {
const overrideOps = overrides(input, index);
if (configIsApplicable(overrideOps, dirname, context)) {
flattenedConfigs.push(overrideOps);
flattenedConfigs.push({
config: overrideOps,
index,
envName: undefined,
});
const overrideEnvOpts = overridesEnv(input, index, context.envName);
if (
overrideEnvOpts &&
configIsApplicable(overrideEnvOpts, dirname, context)
) {
flattenedConfigs.push(overrideEnvOpts);
flattenedConfigs.push({
config: overrideEnvOpts,
index,
envName: context.envName,
});
}
}
});
@ -462,7 +546,7 @@ function makeChainWalker<ArgT: { options: ValidatedOptions, dirname: string }>({
// that we don't do extra work loading extended configs if a file is
// ignored.
if (
flattenedConfigs.some(({ options: { ignore, only } }) =>
flattenedConfigs.some(({ config: { options: { ignore, only } } }) =>
shouldIgnore(context, ignore, only, dirname),
)
) {
@ -470,15 +554,24 @@ function makeChainWalker<ArgT: { options: ValidatedOptions, dirname: string }>({
}
const chain = emptyChain();
const logger = createLogger(input, context, baseLogger);
for (const op of flattenedConfigs) {
for (const { config, index, envName } of flattenedConfigs) {
if (
!(yield* mergeExtendsChain(chain, op.options, dirname, context, files))
!(yield* mergeExtendsChain(
chain,
config.options,
dirname,
context,
files,
baseLogger,
))
) {
return null;
}
mergeChainOpts(chain, op);
logger(config, index, envName);
mergeChainOpts(chain, config);
}
return chain;
};
@ -490,6 +583,7 @@ function* mergeExtendsChain(
dirname: string,
context: ConfigContext,
files: Set<ConfigFile>,
baseLogger: ConfigPrinter | void,
): Handler<boolean> {
if (opts.extends === undefined) return true;
@ -513,6 +607,7 @@ function* mergeExtendsChain(
validateExtendFile(file),
context,
files,
baseLogger,
);
files.delete(file);
@ -650,22 +745,28 @@ function shouldIgnore(
dirname: string,
): boolean {
if (ignore && matchesPatterns(context, ignore, dirname)) {
debug(
"Ignored %o because it matched one of %O from %o",
context.filename,
const message = `No config is applied to "${
context.filename ?? "(unknown)"
}" because it matches one of \`ignore: ${JSON.stringify(
ignore,
dirname,
);
)}\` from "${dirname}"`;
debug(message);
if (context.showConfig) {
console.log(message);
}
return true;
}
if (only && !matchesPatterns(context, only, dirname)) {
debug(
"Ignored %o because it failed to match one of %O from %o",
context.filename,
const message = `No config is applied to "${
context.filename ?? "(unknown)"
}" because it fails to match one of \`only: ${JSON.stringify(
only,
dirname,
);
)}\` from "${dirname}"`;
debug(message);
if (context.showConfig) {
console.log(message);
}
return true;
}

View File

@ -299,6 +299,23 @@ const readIgnoreConfig = makeStaticFileCache((filepath, content) => {
};
});
export function* resolveShowConfigPath(
dirname: string,
): Handler<string | null> {
const targetPath = process.env.BABEL_SHOW_CONFIG_FOR;
if (targetPath != null) {
const absolutePath = path.resolve(dirname, targetPath);
const stats = yield* fs.stat(absolutePath);
if (!stats.isFile()) {
throw new Error(
`${absolutePath}: BABEL_SHOW_CONFIG_FOR must refer to a regular file, directories are not supported.`,
);
}
return absolutePath;
}
return null;
}
function throwConfigError(): empty {
throw new Error(`\
Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured

View File

@ -58,6 +58,13 @@ export function* loadConfig(
throw new Error(`Cannot load ${name} relative to ${dirname} in a browser`);
}
// eslint-disable-next-line require-yield
export function* resolveShowConfigPath(
dirname: string, // eslint-disable-line no-unused-vars
): Handler<string | null> {
return null;
}
export const ROOT_CONFIG_FILENAMES = [];
// eslint-disable-next-line no-unused-vars

View File

@ -14,6 +14,7 @@ export {
findRelativeConfig,
findRootConfig,
loadConfig,
resolveShowConfigPath,
ROOT_CONFIG_FILENAMES,
} from "./configuration";
export type {

View File

@ -15,6 +15,7 @@ import {
import {
findConfigUpwards,
resolveShowConfigPath,
ROOT_CONFIG_FILENAMES,
type ConfigFile,
type IgnoreFile,
@ -89,15 +90,20 @@ export default function* loadPrivatePartialConfig(
rootMode,
);
const filename =
typeof args.filename === "string"
? path.resolve(cwd, args.filename)
: undefined;
const showConfigPath = yield* resolveShowConfigPath(absoluteCwd);
const context: ConfigContext = {
filename:
typeof args.filename === "string"
? path.resolve(cwd, args.filename)
: undefined,
filename,
cwd: absoluteCwd,
root: absoluteRootDir,
envName,
caller,
showConfig: showConfigPath === filename,
};
const configChain = yield* buildRootChain(args, context);

View File

@ -0,0 +1,133 @@
// @flow
import type {
OptionsAndDescriptors,
UnloadedDescriptor,
} from "./config-descriptors";
// todo: Use flow enums when @babel/transform-flow-types supports it
export const ChainFormatter = {
Programmatic: 0,
Config: 1,
};
type PrintableConfig = {
content: OptionsAndDescriptors,
type: $Values<typeof ChainFormatter>,
callerName: ?string,
filepath: ?string,
index: ?number,
envName: ?string,
};
const Formatter = {
title(
type: $Values<typeof ChainFormatter>,
callerName: ?string,
filepath: ?string,
): string {
let title = "";
if (type === ChainFormatter.Programmatic) {
title = "programmatic options";
if (callerName) {
title += " from " + callerName;
}
} else {
// $FlowIgnore
title = "config " + filepath;
}
return title;
},
loc(index: ?number, envName: ?string): string {
let loc = "";
if (index != null) {
loc += `.overrides[${index}]`;
}
if (envName != null) {
loc += `.env["${envName}"]`;
}
return loc;
},
optionsAndDescriptors(opt: OptionsAndDescriptors) {
const content = { ...opt.options };
// overrides and env will be printed as separated config items
delete content.overrides;
delete content.env;
// resolve to descriptors
const pluginDescriptors = [...opt.plugins()];
if (pluginDescriptors.length) {
content.plugins = pluginDescriptors.map(d => descriptorToConfig(d));
}
const presetDescriptors = [...opt.presets()];
if (presetDescriptors.length) {
content.presets = [...presetDescriptors].map(d => descriptorToConfig(d));
}
return JSON.stringify(content, undefined, 2);
},
};
function descriptorToConfig(d: UnloadedDescriptor): string | {} | Array<mixed> {
let name = d.file?.request;
if (name == null) {
if (typeof d.value === "object") {
name = d.value;
} else if (typeof d.value === "function") {
// If the unloaded descriptor is a function, i.e. `plugins: [ require("my-plugin") ]`,
// we print the first 50 characters of the function source code and hopefully we can see
// `name: 'my-plugin'` in the source
name = `[Function: ${d.value.toString().substr(0, 50)} ... ]`;
}
}
if (name == null) {
name = "[Unknown]";
}
if (d.options === undefined) {
return name;
} else if (d.name == null) {
return [name, d.options];
} else {
return [name, d.options, d.name];
}
}
export class ConfigPrinter {
_stack: Array<PrintableConfig> = [];
configure(
enabled: boolean,
type: $Values<typeof ChainFormatter>,
{ callerName, filepath }: { callerName?: string, filepath?: string },
) {
if (!enabled) return () => {};
return (
content: OptionsAndDescriptors,
index: ?number,
envName: ?string,
) => {
this._stack.push({
type,
callerName,
filepath,
content,
index,
envName,
});
};
}
static format(config: PrintableConfig): string {
let title = Formatter.title(
config.type,
config.callerName,
config.filepath,
);
const loc = Formatter.loc(config.index, config.envName);
if (loc) title += ` ${loc}`;
const content = Formatter.optionsAndDescriptors(config.content);
return `${title}\n${content}`;
}
output(): string {
if (this._stack.length === 0) return "";
return this._stack.map(s => ConfigPrinter.format(s)).join("\n\n");
}
}

View File

@ -19,3 +19,8 @@ export const exists = gensync<[string], boolean>({
},
errback: (path, cb) => fs.access(path, undefined, err => cb(null, !err)),
});
export const stat = gensync<[string], *>({
sync: fs.statSync,
errback: fs.stat,
});