Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add readConfigs method to jest-config #7096

Merged
merged 1 commit into from
Oct 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
153 changes: 14 additions & 139 deletions packages/jest-cli/src/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<Path>,
argv: Argv,
outputStream,
): {
globalConfig: GlobalConfig,
configs: Array<ProjectConfig>,
hasDeprecationWarnings: boolean,
} => {
let globalConfig;
let hasDeprecationWarnings;
let configs: Array<ProjectConfig> = [];
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,
Expand Down
7 changes: 7 additions & 0 deletions packages/jest-config/src/__tests__/readConfigs.test.js
Original file line number Diff line number Diff line change
@@ -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.');
});
118 changes: 116 additions & 2 deletions packages/jest-config/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand All @@ -94,7 +96,7 @@ export function readConfig(
};
}

const getConfigs = (
const groupOptions = (
options: Object,
): {globalConfig: GlobalConfig, projectConfig: ProjectConfig} => ({
globalConfig: Object.freeze({
Expand Down Expand Up @@ -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<Path>,
): {
globalConfig: GlobalConfig,
configs: Array<ProjectConfig>,
hasDeprecationWarnings: boolean,
} {
let globalConfig;
let hasDeprecationWarnings;
let configs: Array<ProjectConfig> = [];
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,
};
}