Skip to content

Commit

Permalink
Move the logic to get the configurations for a list of projects from …
Browse files Browse the repository at this point in the history
…jest-cli to jest-config (jestjs#7096)
  • Loading branch information
rubennorte committed Oct 10, 2018
1 parent 179b3e8 commit b4d9ed8
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 141 deletions.
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,
};
}

0 comments on commit b4d9ed8

Please sign in to comment.