diff --git a/CHANGELOG.md b/CHANGELOG.md index c1b6ebd926e3..ae342fcdefae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - `[jest-haste-map]` Add `getFileIterator` to `HasteFS` for faster file iteration ([#7010](https://github.com/facebook/jest/pull/7010)). - `[jest-worker]` [**BREAKING**] Add functionality to call a `setup` method in the worker before the first call and a `teardown` method when ending the farm ([#7014](https://github.com/facebook/jest/pull/7014)). - `[jest-config]` [**BREAKING**] Set default `notifyMode` to `failure-change` ([#7024](https://github.com/facebook/jest/pull/7024)) +- `[jest-config]` Add `readConfigs` function, previously in `jest-cli` ([#7096](https://github.com/facebook/jest/pull/7096)) - `[jest-snapshot]` Enable configurable snapshot paths ([#6143](https://github.com/facebook/jest/pull/6143)) ### Fixes diff --git a/packages/jest-cli/src/cli/index.js b/packages/jest-cli/src/cli/index.js index bba73c181171..889a35f77808 100644 --- a/packages/jest-cli/src/cli/index.js +++ b/packages/jest-cli/src/cli/index.js @@ -9,20 +9,18 @@ import type {AggregatedResult} from 'types/TestResult'; import type {Argv} from 'types/Argv'; -import type {GlobalConfig, Path, ProjectConfig} from 'types/Config'; +import type {GlobalConfig, Path} from 'types/Config'; import {Console, clearLine, createDirectory} from 'jest-util'; import {validateCLIOptions} from 'jest-validate'; -import {readConfig, deprecationEntries} from 'jest-config'; +import {readConfigs, deprecationEntries} from 'jest-config'; import * as args from './args'; import chalk from 'chalk'; import createContext from '../lib/create_context'; import exit from 'exit'; import getChangedFilesPromise from '../getChangedFilesPromise'; import {formatHandleErrors} from '../collectHandles'; -import fs from 'fs'; import handleDeprecationWarnings from '../lib/handle_deprecation_warnings'; -import logDebugMessages from '../lib/log_debug_messages'; import {print as preRunMessagePrint} from '../preRunMessage'; import runJest from '../runJest'; import Runtime from 'jest-runtime'; @@ -33,6 +31,7 @@ import yargs from 'yargs'; import rimraf from 'rimraf'; import {sync as realpath} from 'realpath-native'; import init from '../lib/init'; +import logDebugMessages from '../lib/log_debug_messages'; export async function run(maybeArgv?: Argv, project?: Path) { try { @@ -71,12 +70,20 @@ export const runCLI = async ( const outputStream = argv.json || argv.useStderr ? process.stderr : process.stdout; - const {globalConfig, configs, hasDeprecationWarnings} = getConfigs( - projects, + const {globalConfig, configs, hasDeprecationWarnings} = readConfigs( argv, - outputStream, + projects, ); + if (argv.debug) { + logDebugMessages(globalConfig, configs, outputStream); + } + + if (argv.showConfig) { + logDebugMessages(globalConfig, configs, process.stdout); + exit(0); + } + if (argv.clearCache) { configs.forEach(config => { rimraf.sync(config.cacheDirectory); @@ -201,138 +208,6 @@ const getProjectListFromCLIArgs = (argv, project: ?Path) => { return projects; }; -const printDebugInfoAndExitIfNeeded = ( - argv, - globalConfig, - configs, - outputStream, -) => { - if (argv.debug) { - logDebugMessages(globalConfig, configs, outputStream); - return; - } - - if (argv.showConfig) { - logDebugMessages(globalConfig, configs, process.stdout); - exit(0); - } -}; - -const ensureNoDuplicateConfigs = (parsedConfigs, projects, rootConfigPath) => { - const configPathMap = new Map(); - - for (const config of parsedConfigs) { - const {configPath} = config; - if (configPathMap.has(configPath)) { - const message = `Whoops! Two projects resolved to the same config path: ${chalk.bold( - String(configPath), - )}: - - Project 1: ${chalk.bold(projects[parsedConfigs.findIndex(x => x === config)])} - Project 2: ${chalk.bold( - projects[parsedConfigs.findIndex(x => x === configPathMap.get(configPath))], - )} - -This usually means that your ${chalk.bold( - '"projects"', - )} config includes a directory that doesn't have any configuration recognizable by Jest. Please fix it. -`; - - throw new Error(message); - } - if (configPath !== null) { - configPathMap.set(configPath, config); - } - } -}; - -// Possible scenarios: -// 1. jest --config config.json -// 2. jest --projects p1 p2 -// 3. jest --projects p1 p2 --config config.json -// 4. jest --projects p1 -// 5. jest -// -// If no projects are specified, process.cwd() will be used as the default -// (and only) project. -const getConfigs = ( - projectsFromCLIArgs: Array, - argv: Argv, - outputStream, -): { - globalConfig: GlobalConfig, - configs: Array, - hasDeprecationWarnings: boolean, -} => { - let globalConfig; - let hasDeprecationWarnings; - let configs: Array = []; - let projects = projectsFromCLIArgs; - let configPath: ?Path; - - if (projectsFromCLIArgs.length === 1) { - const parsedConfig = readConfig(argv, projects[0]); - configPath = parsedConfig.configPath; - - if (parsedConfig.globalConfig.projects) { - // If this was a single project, and its config has `projects` - // settings, use that value instead. - projects = parsedConfig.globalConfig.projects; - } - - hasDeprecationWarnings = parsedConfig.hasDeprecationWarnings; - globalConfig = parsedConfig.globalConfig; - configs = [parsedConfig.projectConfig]; - if (globalConfig.projects && globalConfig.projects.length) { - // Even though we had one project in CLI args, there might be more - // projects defined in the config. - projects = globalConfig.projects; - } - } - - if (projects.length > 1) { - const parsedConfigs = projects - .filter(root => { - // Ignore globbed files that cannot be `require`d. - if ( - fs.existsSync(root) && - !fs.lstatSync(root).isDirectory() && - !root.endsWith('.js') && - !root.endsWith('.json') - ) { - return false; - } - - return true; - }) - .map(root => readConfig(argv, root, true, configPath)); - - ensureNoDuplicateConfigs(parsedConfigs, projects, configPath); - configs = parsedConfigs.map(({projectConfig}) => projectConfig); - if (!hasDeprecationWarnings) { - hasDeprecationWarnings = parsedConfigs.some( - ({hasDeprecationWarnings}) => !!hasDeprecationWarnings, - ); - } - // If no config was passed initially, use the one from the first project - if (!globalConfig) { - globalConfig = parsedConfigs[0].globalConfig; - } - } - - if (!globalConfig || !configs.length) { - throw new Error('jest: No configuration found for any project.'); - } - - printDebugInfoAndExitIfNeeded(argv, globalConfig, configs, outputStream); - - return { - configs, - globalConfig, - hasDeprecationWarnings: !!hasDeprecationWarnings, - }; -}; - const buildContextsAndHasteMaps = async ( configs, globalConfig, diff --git a/packages/jest-config/src/__tests__/readConfigs.test.js b/packages/jest-config/src/__tests__/readConfigs.test.js new file mode 100644 index 000000000000..188326c76d48 --- /dev/null +++ b/packages/jest-config/src/__tests__/readConfigs.test.js @@ -0,0 +1,7 @@ +import {readConfigs} from '../index'; + +test('readConfigs() throws when called without project paths', () => { + expect(() => { + readConfigs(null /* argv */, [] /* projectPaths */); + }).toThrowError('jest: No configuration found for any project.'); +}); diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.js index 5138379d9ccd..5ac59c067a8d 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.js @@ -15,6 +15,8 @@ import type { ProjectConfig, } from 'types/Config'; +import chalk from 'chalk'; +import fs from 'fs'; import path from 'path'; import {isJSONString, replaceRootDirInPath} from './utils'; import normalize from './normalize'; @@ -85,7 +87,7 @@ export function readConfig( } const {options, hasDeprecationWarnings} = normalize(rawOptions, argv); - const {globalConfig, projectConfig} = getConfigs(options); + const {globalConfig, projectConfig} = groupOptions(options); return { configPath, globalConfig, @@ -94,7 +96,7 @@ export function readConfig( }; } -const getConfigs = ( +const groupOptions = ( options: Object, ): {globalConfig: GlobalConfig, projectConfig: ProjectConfig} => ({ globalConfig: Object.freeze({ @@ -202,3 +204,115 @@ const getConfigs = ( watchPathIgnorePatterns: options.watchPathIgnorePatterns, }), }); + +const ensureNoDuplicateConfigs = (parsedConfigs, projects, rootConfigPath) => { + const configPathMap = new Map(); + + for (const config of parsedConfigs) { + const {configPath} = config; + if (configPathMap.has(configPath)) { + const message = `Whoops! Two projects resolved to the same config path: ${chalk.bold( + String(configPath), + )}: + + Project 1: ${chalk.bold(projects[parsedConfigs.findIndex(x => x === config)])} + Project 2: ${chalk.bold( + projects[parsedConfigs.findIndex(x => x === configPathMap.get(configPath))], + )} + +This usually means that your ${chalk.bold( + '"projects"', + )} config includes a directory that doesn't have any configuration recognizable by Jest. Please fix it. +`; + + throw new Error(message); + } + if (configPath !== null) { + configPathMap.set(configPath, config); + } + } +}; + +// Possible scenarios: +// 1. jest --config config.json +// 2. jest --projects p1 p2 +// 3. jest --projects p1 p2 --config config.json +// 4. jest --projects p1 +// 5. jest +// +// If no projects are specified, process.cwd() will be used as the default +// (and only) project. +export function readConfigs( + argv: Argv, + projectPaths: Array, +): { + globalConfig: GlobalConfig, + configs: Array, + hasDeprecationWarnings: boolean, +} { + let globalConfig; + let hasDeprecationWarnings; + let configs: Array = []; + let projects = projectPaths; + let configPath: ?Path; + + if (projectPaths.length === 1) { + const parsedConfig = readConfig(argv, projects[0]); + configPath = parsedConfig.configPath; + + if (parsedConfig.globalConfig.projects) { + // If this was a single project, and its config has `projects` + // settings, use that value instead. + projects = parsedConfig.globalConfig.projects; + } + + hasDeprecationWarnings = parsedConfig.hasDeprecationWarnings; + globalConfig = parsedConfig.globalConfig; + configs = [parsedConfig.projectConfig]; + if (globalConfig.projects && globalConfig.projects.length) { + // Even though we had one project in CLI args, there might be more + // projects defined in the config. + projects = globalConfig.projects; + } + } + + if (projects.length > 1) { + const parsedConfigs = projects + .filter(root => { + // Ignore globbed files that cannot be `require`d. + if ( + fs.existsSync(root) && + !fs.lstatSync(root).isDirectory() && + !root.endsWith('.js') && + !root.endsWith('.json') + ) { + return false; + } + + return true; + }) + .map(root => readConfig(argv, root, true, configPath)); + + ensureNoDuplicateConfigs(parsedConfigs, projects, configPath); + configs = parsedConfigs.map(({projectConfig}) => projectConfig); + if (!hasDeprecationWarnings) { + hasDeprecationWarnings = parsedConfigs.some( + ({hasDeprecationWarnings}) => !!hasDeprecationWarnings, + ); + } + // If no config was passed initially, use the one from the first project + if (!globalConfig) { + globalConfig = parsedConfigs[0].globalConfig; + } + } + + if (!globalConfig || !configs.length) { + throw new Error('jest: No configuration found for any project.'); + } + + return { + configs, + globalConfig, + hasDeprecationWarnings: !!hasDeprecationWarnings, + }; +}