From 827b30c070a294b6177ea80bc610d215ee1e71ac Mon Sep 17 00:00:00 2001 From: Logan Smyth Date: Sun, 9 Sep 2018 21:47:28 -0700 Subject: [PATCH] Allow users to choose how the configuration root is selected. --- packages/babel-cli/src/babel/options.js | 6 +++ .../src/config/files/configuration.js | 15 ++++++ .../src/config/files/index-browser.js | 6 +++ packages/babel-core/src/config/files/index.js | 1 + packages/babel-core/src/config/partial.js | 51 +++++++++++++++++-- .../config/validation/option-assertions.js | 15 ++++++ .../src/config/validation/options.js | 6 +++ 7 files changed, 96 insertions(+), 4 deletions(-) diff --git a/packages/babel-cli/src/babel/options.js b/packages/babel-cli/src/babel/options.js index b15d088e98d4..16a9c025bcd9 100644 --- a/packages/babel-cli/src/babel/options.js +++ b/packages/babel-cli/src/babel/options.js @@ -28,6 +28,11 @@ commander.option( "The name of the 'env' to use when loading configs and plugins. " + "Defaults to the value of BABEL_ENV, or else NODE_ENV, or else 'development'.", ); +commander.option( + "--root-mode [mode]", + "The project-root resolution mode. " + + "One of 'root' (the default), 'upward', or 'upward-optional'.", +); // Basic file input configuration. commander.option("--source-type [script|module]", ""); @@ -220,6 +225,7 @@ export default function parseArgv(args: Array) { babelOptions: { presets: opts.presets, plugins: opts.plugins, + rootMode: opts.rootMode, configFile: opts.configFile, envName: opts.envName, sourceType: opts.sourceType, diff --git a/packages/babel-core/src/config/files/configuration.js b/packages/babel-core/src/config/files/configuration.js index b31519b43f97..279da6b7cf08 100644 --- a/packages/babel-core/src/config/files/configuration.js +++ b/packages/babel-core/src/config/files/configuration.js @@ -24,6 +24,21 @@ const BABELRC_FILENAME = ".babelrc"; const BABELRC_JS_FILENAME = ".babelrc.js"; const BABELIGNORE_FILENAME = ".babelignore"; +export function findConfigUpwards(rootDir: string): string | null { + let dirname = rootDir; + while (true) { + if (fs.existsSync(path.join(dirname, BABEL_CONFIG_JS_FILENAME))) { + return dirname; + } + + const nextDir = path.dirname(dirname); + if (dirname === nextDir) break; + dirname = nextDir; + } + + return null; +} + export function findRelativeConfig( packageData: FilePackageData, envName: string, diff --git a/packages/babel-core/src/config/files/index-browser.js b/packages/babel-core/src/config/files/index-browser.js index 3305c346e43a..1d2adccaa811 100644 --- a/packages/babel-core/src/config/files/index-browser.js +++ b/packages/babel-core/src/config/files/index-browser.js @@ -11,6 +11,12 @@ import type { CallerMetadata } from "../validation/options"; export type { ConfigFile, IgnoreFile, RelativeConfig, FilePackageData }; +export function findConfigUpwards( + rootDir: string, // eslint-disable-line no-unused-vars +): string | null { + return null; +} + export function findPackageData(filepath: string): FilePackageData { return { filepath, diff --git a/packages/babel-core/src/config/files/index.js b/packages/babel-core/src/config/files/index.js index 9f8d9797bbfb..3c74f2d4eebb 100644 --- a/packages/babel-core/src/config/files/index.js +++ b/packages/babel-core/src/config/files/index.js @@ -10,6 +10,7 @@ import typeof * as indexType from "./index"; export { findPackageData } from "./package"; export { + findConfigUpwards, findRelativeConfig, findRootConfig, loadConfig, diff --git a/packages/babel-core/src/config/partial.js b/packages/babel-core/src/config/partial.js index 31b2aa0cf432..818a60a5ddf9 100644 --- a/packages/babel-core/src/config/partial.js +++ b/packages/babel-core/src/config/partial.js @@ -6,9 +6,43 @@ import { mergeOptions } from "./util"; import { createItemFromDescriptor } from "./item"; import { buildRootChain, type ConfigContext } from "./config-chain"; import { getEnv } from "./helpers/environment"; -import { validate, type ValidatedOptions } from "./validation/options"; +import { + validate, + type ValidatedOptions, + type RootMode, +} from "./validation/options"; + +import { findConfigUpwards, type ConfigFile, type IgnoreFile } from "./files"; + +function resolveRootMode(rootDir: string, rootMode: RootMode): string { + switch (rootMode) { + case "root": + return rootDir; + + case "upward-optional": { + const upwardRootDir = findConfigUpwards(rootDir); + return upwardRootDir === null ? rootDir : upwardRootDir; + } -import type { ConfigFile, IgnoreFile } from "./files"; + case "upward": { + const upwardRootDir = findConfigUpwards(rootDir); + if (upwardRootDir !== null) return upwardRootDir; + + throw Object.assign( + (new Error( + `Babel was run with rootMode:"${rootMode}" but a root could not ` + + `be found when searching upward from "${rootDir}"`, + ): any), + { + code: "BABEL_ROOT_NOT_FOUND", + dirname: rootDir, + }, + ); + } + default: + throw new Error(`Assertion failure - unknown rootMode value`); + } +} export default function loadPrivatePartialConfig( inputOpts: mixed, @@ -28,9 +62,18 @@ export default function loadPrivatePartialConfig( const args = inputOpts ? validate("arguments", inputOpts) : {}; - const { envName = getEnv(), cwd = ".", root: rootDir = ".", caller } = args; + const { + envName = getEnv(), + cwd = ".", + root: rootDir = ".", + rootMode = "root", + caller, + } = args; const absoluteCwd = path.resolve(cwd); - const absoluteRootDir = path.resolve(absoluteCwd, rootDir); + const absoluteRootDir = resolveRootMode( + path.resolve(absoluteCwd, rootDir), + rootMode, + ); const context: ConfigContext = { filename: diff --git a/packages/babel-core/src/config/validation/option-assertions.js b/packages/babel-core/src/config/validation/option-assertions.js index e8f51d2d123a..321e1c063229 100644 --- a/packages/babel-core/src/config/validation/option-assertions.js +++ b/packages/babel-core/src/config/validation/option-assertions.js @@ -15,6 +15,7 @@ import type { RootInputSourceMapOption, NestingPath, CallerMetadata, + RootMode, } from "./options"; export type ValidatorSet = { @@ -60,6 +61,20 @@ type AccessPath = $ReadOnly<{ }>; type GeneralPath = OptionPath | AccessPath; +export function assertRootMode(loc: OptionPath, value: mixed): RootMode | void { + if ( + value !== undefined && + value !== "root" && + value !== "upward" && + value !== "upward-optional" + ) { + throw new Error( + `${msg(loc)} must be a "root", "upward", "upward-optional" or undefined`, + ); + } + return value; +} + export function assertSourceMaps( loc: OptionPath, value: mixed, diff --git a/packages/babel-core/src/config/validation/options.js b/packages/babel-core/src/config/validation/options.js index ee0e04638ea3..c790adfac9f9 100644 --- a/packages/babel-core/src/config/validation/options.js +++ b/packages/babel-core/src/config/validation/options.js @@ -19,6 +19,7 @@ import { assertConfigFileSearch, assertBabelrcSearch, assertFunction, + assertRootMode, assertSourceMaps, assertCompact, assertSourceType, @@ -30,6 +31,9 @@ import { const ROOT_VALIDATORS: ValidatorSet = { cwd: (assertString: Validator<$PropertyType>), root: (assertString: Validator<$PropertyType>), + rootMode: (assertRootMode: Validator< + $PropertyType, + >), configFile: (assertConfigFileSearch: Validator< $PropertyType, >), @@ -176,6 +180,7 @@ export type ValidatedOptions = { babelrcRoots?: BabelrcSearch, configFile?: ConfigFileSearch, root?: string, + rootMode?: RootMode, code?: boolean, ast?: boolean, inputSourceMap?: RootInputSourceMapOption, @@ -260,6 +265,7 @@ export type SourceMapsOption = boolean | "inline" | "both"; export type SourceTypeOption = "module" | "script" | "unambiguous"; export type CompactOption = boolean | "auto"; export type RootInputSourceMapOption = {} | boolean; +export type RootMode = "root" | "upward" | "upward-optional"; export type OptionsSource = | "arguments"